feat(hooks): expose per-step ToolChoice via OnPrepareStep#82
Conversation
|
Connected to Huly®: KIT-83 |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR adds a per-step tool-choice override capability. ChangesPer-step ToolChoice override
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Motivation
fantasy supports per-step tool-choice control (
PrepareStepResult.ToolChoice,applied in
agent.gostep loop), but kit's publicOnPrepareStephook onlyexposes message replacement — the internal
PrepareStepclosure builds thefantasy result with Model/Messages/Tools and never threads a tool choice.
This gap matters for structured-output / capture-tool patterns: an agent whose
turn must end with a specific tool call (e.g.
record_result) can currentlyonly be prompted to call it, and models occasionally finish a turn without
doing so. Per-step forcing closes that gap: force
SpecificToolChoice(tool)while the capture hasn't happened, release once it has.
Per-step (rather than session-level) control is deliberate — a turn only ends
when the model stops emitting tool calls, so a permanently forced choice can
never legally finish a turn. The hook is the right granularity.
Changes
pkg/kit/types.go— re-exportfantasy.ToolChoiceasLLMToolChoicewith the
None/Auto/Requiredconstants and anLLMSpecificToolChoiceconstructor var, all following the existing LLM-prefixed re-export convention.
pkg/kit/hooks.go— addToolChoice *LLMToolChoicetoPrepareStepResult(nil = provider default, unchanged behaviour). Doccomment warns about the always-forced footgun.
internal/agent/agent.go—PrepareStepHandlernow returns a*PrepareStepUpdatestruct (Messages + ToolChoice, nil fields = no change)instead of bare replacement messages; the
PrepareStepclosure threadsToolChoiceinto fantasy'sPrepareStepResult.pkg/kit/kit.go— the hook→handler bridge forwardsToolChoiceandreturns a non-nil update when either field is set.
Compatibility
PrepareStepResultgains a field; all existingconstruction uses named fields. Existing hooks returning Messages-only
results behave identically.
PrepareStepHandlersignature changes, but it is module-internalwith a single producer (kit.go bridge) and single consumer (agent closure),
both updated.
extensions_bridge.go) untouched — extension-facingToolChoice exposure can follow separately if wanted.
Tests
internal/agent/agent_toolchoice_test.go— fake fantasy.Agent records theToolChoice each step's PrepareStep yields: forced on step 0, released on
step 1; nil throughout when no handler is registered. Mirrors the
agent_midturn_tools_test.goharness.pkg/kit/hooks_test.go— registry-level test for a ToolChoice-only result(Messages nil passes through unchanged).
Summary by CodeRabbit