feat(llm): handle LLM tool call responses with execution loop #137
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
thwap/kronai!137
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/0095-handle-tool-calls"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Handle LLM tool call responses: detect
tool_callsin responses, execute tools via MCP servers, send results back to the LLM, and return the final text response.Why
Previously, tool call responses with null
contentwould return the string"None". Now tool calls are properly executed in a multi-turn loop.Changes
_extract_texthandlesNonecontent gracefullycomplete()acceptsexecute_toolcallback for tool call loopMCPServer.call_tool()for executing MCP tool callsTesting
Closes #95
74f2ce94162e4c8cd3282e4c8cd328d0b8e3324dd0b8e3324d649de4703b649de4703bab0ad4ff7cab0ad4ff7ce3a1f1dae8e3a1f1dae855622a07d055622a07d0bae0bd821bbae0bd821b8e165c5fc58e165c5fc55babba5057Automated Review — PR #137
Result: REQUEST_CHANGES
This is a substantial and important PR — the tool call execution loop enables proper multi-turn LLM conversations with MCP tool integration. The overall design is sound, but there is one blocking issue.
Good parts
_send_and_parse()cleanly separates HTTP from response parsing_extract_text()handles None content gracefully (fixes the"None"string bug)MCPServer.call_tool()follows the existing_send_requestpattern_init_mcp_servers()refactor is a nice cleanupBlocking Issue
src/kronai/engine.py:86-92— MCPError during tool execution leaks server connections.The
_execute_toolclosure callsserver.call_tool()which can raiseMCPError. This propagates throughcomplete()and out, butrun_job()only catchesLLMError:If the MCP server fails mid-tool-call,
MCPErrorbypasses the cleanup and status/notification calls. The subprocess is leaked.Options to fix:
_execute_toolcatchMCPErrorand raiseLLMErrorinstead (or return an error string)except (LLMError, MCPError, JobError)to the outer handlercomplete()accept a broader exception type or wrap tool execution errorsNon-blocking suggestions
src/kronai/engine.py:90—str(server.call_tool(...))—call_toolalready returnsstr. Redundantstr()call is harmless but unnecessary.src/kronai/llm.py:96—max_tool_rounds = 10is hardcoded. Consider making it a constructor parameter (likemax_retriesandbase_delayin PR #130).Automated Review — PR #137 (re-review)
Result: APPROVED
Blocking issue resolved.
_execute_toolnow wrapsMCPErrorinLLMError, so tool execution failures are caught by the existing cleanup/status/notification handler inrun_job(). Server connections are no longer leaked.Additionally,
max_tool_roundsis now a constructor parameter matching the pattern frommax_retries/base_delay. Redundantstr()tracked in #146.No remaining blocking issues.