306f114b8a
CI / lint (pull_request) Successful in 47s
CI / typecheck (pull_request) Successful in 51s
CI / security (pull_request) Successful in 50s
CI / quality (pull_request) Successful in 33s
CI / unit_tests (pull_request) Successful in 3m7s
CI / integration_tests (pull_request) Successful in 1m10s
CI / build (pull_request) Successful in 35s
CI / coverage (pull_request) Successful in 3m9s
CI / status-check (pull_request) Successful in 3s
CI / lint (push) Successful in 34s
CI / typecheck (push) Successful in 51s
CI / security (push) Successful in 50s
CI / quality (push) Successful in 33s
CI / unit_tests (push) Successful in 3m6s
CI / integration_tests (push) Successful in 1m7s
CI / build (push) Successful in 33s
CI / coverage (push) Successful in 3m7s
CI / status-check (push) Successful in 3s
Extract the entire multi-turn tool-call orchestration from the inline loop inside process_message() into a new shared private method _execute_tool_loop(). Both process_message() and stream_message() now delegate to this single implementation, eliminating the previous capability gap where stream_message() had no tool-calling support. Design: - _ToolLoopResult dataclass: carries final_response, accumulated_prompt, accumulated_completion, budget_exhausted, and synthesis_was_run. Both callers read token counts from this object instead of local sentinels. - _ToolLoopError exception: wraps any exception raised inside the helper and carries partial accumulated token counts + any_invocation_made flag. Callers catch this, set their _captured_prompt/_captured_completion sentinels (billing integrity), then re-raise the original cause. - _execute_tool_loop(): extracted verbatim from process_message(). Contains the full ainvoke() loop, per-tool ToolAgent dispatch, token accumulation across rounds, token-budget exhaustion + synthesis path (§4.4.7 D-4), stuck-model synthesis prompt, and tool-output pruning pass (§4.4.8). On exception, wraps in _ToolLoopError for the caller. process_message() refactor: - Replaces ~490 lines of inline tool loop with a 15-line delegation. For tool-configured actors: awaits _execute_tool_loop(), extracts response text and token counts from the result. For no-tool actors: single plain ainvoke() (unchanged behaviour). Billing-integrity sentinels (_captured_prompt/_captured_completion) are set from _ToolLoopError on exception and from the result on success. All existing Behave scenarios continue to pass unmodified. stream_message() integration: - When tools are configured: awaits _execute_tool_loop() and yields the final response as a single content chunk. Token counts come from the result's accumulated_prompt/accumulated_completion fields (spanning all tool rounds + pruning passes). Memory update is performed before the generator returns. Billing sentinels are updated from _ToolLoopError on exception. - When no tools are configured: the existing astream() token-by-token path runs completely unchanged. Single-chunk trade-off: when tools are involved, the user already waits for tool execution before the final answer begins, so yielding that answer as one chunk vs. token-by-token has minimal UX impact. The alternative (re-calling astream() after ainvoke() produced the final response) would double LLM cost for every tool-using request. Webapp workaround: the actor_config_has_tools fallback in cleveragents/cleveragents-webapp#328 (stream_fallback_to_non_stream in _execute_via_cleveractors_stream / _generate_actor_sse) can now be removed. Tests added: - features/llm_agent_tool_loop.feature + steps: 10 Behave scenarios covering _execute_tool_loop() internals independently (single round, multi-round, max_rounds exhaustion, token accumulation, budget exhaustion, stuck-model synthesis, pruning pass, tool dispatch error, exception path with partial counts, pre-invocation ConfigurationError). - features/llm_agent_stream_tool_calls.feature + steps: 7 Behave scenarios for stream_message() with tool-call support (single chunk output, two-round token sum, final text, no-tools astream unchanged, no-tools token counts, exception billing integrity, memory update). - robot/llm_tool_calling.robot: two new Robot integration tests: 'Streaming Path Exercises Tool Loop Via Execute Stream' and 'Streaming Path Accumulates Tokens Across Two Tool Rounds'. - robot/ToolCallingTestLib.py: new keywords and create_executor_with_multi_round_tool_calling_agent factory. Quality gates: format ✓ lint ✓ typecheck ✓ security_scan ✓ unit_tests ✓ integration_tests ✓ coverage_report ✓ (97.0%) ISSUES CLOSED: #67
65 lines
3.2 KiB
Plaintext
65 lines
3.2 KiB
Plaintext
*** Settings ***
|
|
Documentation LLM tool-calling integration tests.
|
|
... Exercises the full pipeline (Executor → LLMAgent → ToolAgent)
|
|
... with a mock chat model that returns tool_calls, so no real
|
|
... LLM API key is required.
|
|
Library ToolCallingTestLib.py
|
|
|
|
*** Test Cases ***
|
|
LLM Agent Receives Tools And Executes Tool Call
|
|
[Documentation] Full lifecycle: config with echo tool → mock returns
|
|
... tool_calls → ToolAgent executes echo → final response produced.
|
|
Create Executor With Tool Calling Agent tool_name=echo message=hello_from_tool
|
|
Execute With Message Tell me something using the echo tool
|
|
Result Is Valid Actor Result
|
|
Result Has Nodes
|
|
Chat Model Was Invoked With Tools
|
|
Multiple Tool Rounds Occurred
|
|
Result Response Contains Final answer after tool use
|
|
|
|
LLM Agent Tool Calling Works With MATH Tool
|
|
[Documentation] MATH tool recognises the expression and computes result.
|
|
Create Executor With Tool Calling Agent tool_name=math message=2+2
|
|
Execute With Message What is 2+2? Use the math tool.
|
|
Result Is Valid Actor Result
|
|
Chat Model Was Invoked With Tools
|
|
Multiple Tool Rounds Occurred
|
|
Result Response Contains Final answer after tool use
|
|
|
|
LLM Agent With FILE_READ Tool Receives Tool Calls
|
|
[Documentation] file_read tool is normalised and passed to the model.
|
|
Create Executor With Tool Calling Agent tool_name=file_read message=some_file.txt
|
|
Execute With Message Read the file some_file.txt for me
|
|
Result Is Valid Actor Result
|
|
Chat Model Was Invoked With Tools
|
|
|
|
Tool Calling Produces Non-Empty Final Response
|
|
[Documentation] After tool execution, the final response is populated.
|
|
Create Executor With Tool Calling Agent tool_name=echo message=tool_data
|
|
Execute With Message Use the echo tool and report back
|
|
Result Is Valid Actor Result
|
|
Tool Call Result Is In Response
|
|
Result Prompt Tokens Greater Than Zero
|
|
|
|
Streaming Path Exercises Tool Loop Via Execute Stream
|
|
[Documentation] execute_stream on a tool-declaring single-LLM actor exercises
|
|
... the _execute_tool_loop() path (issue #67): the tool-call loop runs via
|
|
... ainvoke(), the final response is yielded as a single content chunk, and
|
|
... executor.last_result reports accumulated token counts covering all rounds.
|
|
Create Executor With Tool Calling Agent tool_name=echo message=from_stream
|
|
Execute Stream With Message Tell me about yourself
|
|
Result Is Valid Actor Result
|
|
Result Has Nodes
|
|
Stream Result Response Is Non Empty
|
|
Stream Result Prompt Tokens Greater Than Zero
|
|
|
|
Streaming Path Accumulates Tokens Across Two Tool Rounds
|
|
[Documentation] When the mock LLM returns tool_calls for two rounds before the
|
|
... final answer, execute_stream yields the final answer and the accumulated
|
|
... prompt_tokens equals the sum across all three invocations.
|
|
Create Executor With Multi Round Tool Calling Agent tool_name=echo rounds=2
|
|
Execute Stream With Message Multi round test
|
|
Result Is Valid Actor Result
|
|
Stream Result Response Contains Multi round final answer
|
|
Stream Result Prompt Tokens Equals Multi Round Sum
|