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

poe: fix long prompts

+88 -44
+88 -44
lib/changelog.ml
··· 163 163 Log.warn (fun m -> m "Failed to get channel members: %s" (Printexc.to_string e)); 164 164 [] 165 165 166 - let create_claude_client ~sw ~proc ~clock = 166 + let create_claude_client ~sw ~proc ~clock ?(allow_read=false) () = 167 167 let options = 168 168 Claude.Options.default 169 169 |> Claude.Options.with_model `Opus_4_5 170 170 |> Claude.Options.with_permission_mode Claude.Permissions.Mode.Bypass_permissions 171 - |> Claude.Options.with_allowed_tools [] 171 + |> Claude.Options.with_allowed_tools (if allow_read then ["Read"] else []) 172 172 in 173 173 Claude.Client.create ~options ~sw ~process_mgr:proc ~clock () 174 174 175 - let ask_claude ~sw ~proc ~clock prompt = 176 - let client = create_claude_client ~sw ~proc ~clock in 175 + let ask_claude ~sw ~proc ~clock ?(allow_read=false) prompt = 176 + let client = create_claude_client ~sw ~proc ~clock ~allow_read () in 177 177 Claude.Client.query client prompt; 178 178 let responses = Claude.Client.receive_all client in 179 179 let text = ··· 295 295 end; 296 296 String.trim (Buffer.contents buf) 297 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 + 298 348 let generate ~sw ~proc ~clock ~fs ~commits ~members ?(fork_context=[]) ?opamrepo_path () = 299 349 if commits = [] then None 300 350 else begin ··· 337 387 in 338 388 339 389 let has_forks = fork_context <> [] in 390 + let instructions = build_instructions ~subprojects_text ~fork_context_text ~members_text ~has_forks in 340 391 341 - let prompt = Printf.sprintf 342 - {|You are writing a changelog update for a Zulip channel about a monorepo. 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 343 395 344 - Git commits: 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); 345 406 346 - %s 407 + let prompt = Printf.sprintf 408 + {|You are writing a changelog update for a Zulip channel about a monorepo. 347 409 348 - Affected sub-projects: %s 410 + The git commit data is too large to include inline. Please read it from this file: 349 411 %s 350 - Channel members who can be @mentioned (use exact @**Name** format): 351 412 352 - %s 413 + After reading the file, generate the changelog. 353 414 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. 415 + %s|} tmp_file instructions 416 + in 358 417 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) 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. 363 426 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 - ``` 427 + Git commits: 378 428 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 429 + %s 386 430 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.") 431 + %s|} commits_text instructions 432 + in 433 + ask_claude ~sw ~proc ~clock prompt 434 + end 390 435 in 391 436 392 - let response = ask_claude ~sw ~proc ~clock prompt in 393 437 Log.info (fun m -> m "Claude generated: %s" response); 394 438 395 439 let last_date = most_recent_date commits in