A Zulip bot agent to sit in our Black Sun. Ever evolving

poe: fix long prompts

+88 -44
+88 -44
lib/changelog.ml
··· 163 Log.warn (fun m -> m "Failed to get channel members: %s" (Printexc.to_string e)); 164 [] 165 166 - let create_claude_client ~sw ~proc ~clock = 167 let options = 168 Claude.Options.default 169 |> Claude.Options.with_model `Opus_4_5 170 |> Claude.Options.with_permission_mode Claude.Permissions.Mode.Bypass_permissions 171 - |> Claude.Options.with_allowed_tools [] 172 in 173 Claude.Client.create ~options ~sw ~process_mgr:proc ~clock () 174 175 - let ask_claude ~sw ~proc ~clock prompt = 176 - let client = create_claude_client ~sw ~proc ~clock in 177 Claude.Client.query client prompt; 178 let responses = Claude.Client.receive_all client in 179 let text = ··· 295 end; 296 String.trim (Buffer.contents buf) 297 298 let generate ~sw ~proc ~clock ~fs ~commits ~members ?(fork_context=[]) ?opamrepo_path () = 299 if commits = [] then None 300 else begin ··· 337 in 338 339 let has_forks = fork_context <> [] in 340 341 - let prompt = Printf.sprintf 342 - {|You are writing a changelog update for a Zulip channel about a monorepo. 343 344 - Git commits: 345 346 - %s 347 348 - Affected sub-projects: %s 349 %s 350 - Channel members who can be @mentioned (use exact @**Name** format): 351 352 - %s 353 354 - Write a changelog as a JSON object with three sections: 355 - - "fork_activity": array of objects with "source" (upstream owner handle), "target" (person who changed the fork), and "items" (array of changelog items for repos that are forks of another user). %s 356 - - "functionality": array of changelog items for feature additions, bug fixes, enhancements, and other functional changes (NOT forks). Give these expanded descriptions (2-3 sentences). 357 - - "metadata": array of project name strings for changes that are purely metadata (documentation, CI, formatting, version bumps, opam file updates, .ocamlformat changes, README updates). Just list the project names, no descriptions. 358 359 - Each changelog item has: 360 - - "project": the project/package name (string) 361 - - "description": description of the change, may include @**Name** mentions (string) 362 - - "change_type": one of "new feature", "bug fix", "enhancement", "refactoring" (string) 363 364 - Example output: 365 - ```json 366 - { 367 - "fork_activity": [ 368 - {"source": "anil.recoil.org", "target": "gazagnaire.org", "items": [ 369 - {"project": "ocaml-mdns", "description": "Refactored DNS query pipeline to use Eio effects.", "change_type": "enhancement"} 370 - ]} 371 - ], 372 - "functionality": [ 373 - {"project": "ocaml-claudeio", "description": "Added model types for Opus 4.5 and 4.1. This extends the client to support the latest Claude model lineup.", "change_type": "new feature"} 374 - ], 375 - "metadata": ["ocaml-dns", "dune"] 376 - } 377 - ``` 378 379 - Guidelines: 380 - 1. One item per logical change (group related commits) 381 - 2. Functionality items should have 2-3 sentence descriptions explaining the change and its purpose 382 - 3. Use @**Name** mentions in the description when authors match channel members 383 - 4. No emojis 384 - 5. Put documentation, CI, formatting, version bumps, and opam metadata changes in "metadata" not "functionality" 385 - 6. If a change is to a forked repo, put it in "fork_activity" grouped by source/target pair 386 387 - Output ONLY the JSON object, no markdown code fences or other text.|} commits_text subprojects_text fork_context_text members_text 388 - (if has_forks then "Group items by (source, target) pair based on the fork relationships listed above." 389 - else "Leave empty if no fork relationships exist.") 390 in 391 392 - let response = ask_claude ~sw ~proc ~clock prompt in 393 Log.info (fun m -> m "Claude generated: %s" response); 394 395 let last_date = most_recent_date commits in
··· 163 Log.warn (fun m -> m "Failed to get channel members: %s" (Printexc.to_string e)); 164 [] 165 166 + let create_claude_client ~sw ~proc ~clock ?(allow_read=false) () = 167 let options = 168 Claude.Options.default 169 |> Claude.Options.with_model `Opus_4_5 170 |> Claude.Options.with_permission_mode Claude.Permissions.Mode.Bypass_permissions 171 + |> Claude.Options.with_allowed_tools (if allow_read then ["Read"] else []) 172 in 173 Claude.Client.create ~options ~sw ~process_mgr:proc ~clock () 174 175 + let ask_claude ~sw ~proc ~clock ?(allow_read=false) prompt = 176 + let client = create_claude_client ~sw ~proc ~clock ~allow_read () in 177 Claude.Client.query client prompt; 178 let responses = Claude.Client.receive_all client in 179 let text = ··· 295 end; 296 String.trim (Buffer.contents buf) 297 298 + (* Threshold for using staged file approach (50KB) *) 299 + let large_prompt_threshold = 50_000 300 + 301 + (* Build the common instructions part of the prompt *) 302 + let build_instructions ~subprojects_text ~fork_context_text ~members_text ~has_forks = 303 + Printf.sprintf 304 + {|Write a changelog as a JSON object with three sections: 305 + - "fork_activity": array of objects with "source" (upstream owner handle), "target" (person who changed the fork), and "items" (array of changelog items for repos that are forks of another user). %s 306 + - "functionality": array of changelog items for feature additions, bug fixes, enhancements, and other functional changes (NOT forks). Give these expanded descriptions (2-3 sentences). 307 + - "metadata": array of project name strings for changes that are purely metadata (documentation, CI, formatting, version bumps, opam file updates, .ocamlformat changes, README updates). Just list the project names, no descriptions. 308 + 309 + Affected sub-projects: %s 310 + %s 311 + Channel members who can be @mentioned (use exact @**Name** format): 312 + 313 + %s 314 + 315 + Each changelog item has: 316 + - "project": the project/package name (string) 317 + - "description": description of the change, may include @**Name** mentions (string) 318 + - "change_type": one of "new feature", "bug fix", "enhancement", "refactoring" (string) 319 + 320 + Example output: 321 + ```json 322 + { 323 + "fork_activity": [ 324 + {"source": "anil.recoil.org", "target": "gazagnaire.org", "items": [ 325 + {"project": "ocaml-mdns", "description": "Refactored DNS query pipeline to use Eio effects.", "change_type": "enhancement"} 326 + ]} 327 + ], 328 + "functionality": [ 329 + {"project": "ocaml-claudeio", "description": "Added model types for Opus 4.5 and 4.1. This extends the client to support the latest Claude model lineup.", "change_type": "new feature"} 330 + ], 331 + "metadata": ["ocaml-dns", "dune"] 332 + } 333 + ``` 334 + 335 + Guidelines: 336 + 1. One item per logical change (group related commits) 337 + 2. Functionality items should have 2-3 sentence descriptions explaining the change and its purpose 338 + 3. Use @**Name** mentions in the description when authors match channel members 339 + 4. No emojis 340 + 5. Put documentation, CI, formatting, version bumps, and opam metadata changes in "metadata" not "functionality" 341 + 6. If a change is to a forked repo, put it in "fork_activity" grouped by source/target pair 342 + 343 + Output ONLY the JSON object, no markdown code fences or other text.|} 344 + (if has_forks then "Group items by (source, target) pair based on the fork relationships listed above." 345 + else "Leave empty if no fork relationships exist.") 346 + subprojects_text fork_context_text members_text 347 + 348 let generate ~sw ~proc ~clock ~fs ~commits ~members ?(fork_context=[]) ?opamrepo_path () = 349 if commits = [] then None 350 else begin ··· 387 in 388 389 let has_forks = fork_context <> [] in 390 + let instructions = build_instructions ~subprojects_text ~fork_context_text ~members_text ~has_forks in 391 392 + (* Check if we need to stage the commit data in a file *) 393 + let commits_size = String.length commits_text in 394 + let use_staged_file = commits_size > large_prompt_threshold in 395 396 + let response = 397 + if use_staged_file then begin 398 + (* Write commits to a temporary file and ask Claude to read it *) 399 + Log.info (fun m -> m "Large commit data (%d bytes), staging in temporary file" commits_size); 400 + let tmp_dir = Filename.get_temp_dir_name () in 401 + let tmp_file = Filename.concat tmp_dir (Printf.sprintf "poe-commits-%d.txt" (Unix.getpid ())) in 402 + (* Write the file using Eio *) 403 + let tmp_path = Eio.Path.(fs / tmp_file) in 404 + Eio.Path.save ~create:(`Or_truncate 0o644) tmp_path commits_text; 405 + Log.info (fun m -> m "Staged commit data to %s" tmp_file); 406 407 + let prompt = Printf.sprintf 408 + {|You are writing a changelog update for a Zulip channel about a monorepo. 409 410 + The git commit data is too large to include inline. Please read it from this file: 411 %s 412 413 + After reading the file, generate the changelog. 414 415 + %s|} tmp_file instructions 416 + in 417 418 + let result = ask_claude ~sw ~proc ~clock ~allow_read:true prompt in 419 + (* Clean up the temporary file *) 420 + (try Eio.Path.unlink tmp_path with _ -> ()); 421 + result 422 + end else begin 423 + (* Small enough to include inline *) 424 + let prompt = Printf.sprintf 425 + {|You are writing a changelog update for a Zulip channel about a monorepo. 426 427 + Git commits: 428 429 + %s 430 431 + %s|} commits_text instructions 432 + in 433 + ask_claude ~sw ~proc ~clock prompt 434 + end 435 in 436 437 Log.info (fun m -> m "Claude generated: %s" response); 438 439 let last_date = most_recent_date commits in