Skip to content

Commit 33a6f14

Browse files
committed
fix: show tool names in history and return the model answer
Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
1 parent 6603ba3 commit 33a6f14

6 files changed

Lines changed: 104 additions & 39 deletions

File tree

samples/go/agents/deepresearch/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ require (
3939
go.opentelemetry.io/otel/trace v1.43.0 // indirect
4040
golang.org/x/crypto v0.51.0 // indirect
4141
golang.org/x/mod v0.35.0 // indirect
42-
golang.org/x/net v0.54.0 // indirect
43-
golang.org/x/sys v0.44.0 // indirect
42+
golang.org/x/net v0.55.0 // indirect
43+
golang.org/x/sys v0.45.0 // indirect
4444
golang.org/x/text v0.37.0 // indirect
4545
google.golang.org/api v0.279.0 // indirect
4646
google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect

samples/go/agents/deepresearch/go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
7373
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
7474
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
7575
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
76-
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
77-
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
76+
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
77+
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
7878
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
7979
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
80-
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
81-
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
80+
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
81+
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
8282
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
8383
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
8484
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=

samples/python/agents/a2a-mcp-without-framework/src/no_llm_framework/server/agent.py

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@
2626
called_tools_history_template = Template(f.read())
2727

2828

29+
def build_called_tool_record(tool: dict[str, Any], result: CallToolResult) -> dict[str, Any]:
30+
"""Build a called-tool history record matching called_tools_history.jinja."""
31+
return {
32+
'name': tool['name'],
33+
'arguments': tool['arguments'],
34+
'isError': result.isError,
35+
'result': result.content[0].text,
36+
}
37+
38+
2939
def stream_llm(prompt: str) -> Generator[str, None]:
3040
"""Stream LLM response.
3141
@@ -80,9 +90,7 @@ async def decide(
8090
return self.call_llm(question)
8191
tool_prompt = await get_mcp_tool_prompt(self.mcp_url)
8292
if called_tools:
83-
called_tools_prompt = called_tools_history_template.render(
84-
called_tools=called_tools
85-
)
93+
called_tools_prompt = called_tools_history_template.render(called_tools=called_tools)
8694
else:
8795
called_tools_prompt = ''
8896

@@ -112,10 +120,7 @@ async def call_tool(self, tools: list[dict]) -> list[CallToolResult]:
112120
tools (list[dict]): The tools to call.
113121
"""
114122
return await asyncio.gather(
115-
*[
116-
call_mcp_tool(self.mcp_url, tool['name'], tool['arguments'])
117-
for tool in tools
118-
]
123+
*[call_mcp_tool(self.mcp_url, tool['name'], tool['arguments']) for tool in tools]
119124
)
120125

121126
async def stream(self, question: str) -> AsyncGenerator[dict[str, Any]]:
@@ -128,48 +133,29 @@ async def stream(self, question: str) -> AsyncGenerator[dict[str, Any]]:
128133
dict: Streaming output, including intermediate steps and final result.
129134
"""
130135
called_tools = []
131-
for i in range(10):
132-
yield {
133-
'is_task_complete': False,
134-
'require_user_input': False,
135-
'content': f'Step {i}',
136-
}
137-
138-
response = ''
136+
last_response = ''
137+
for _ in range(10):
138+
last_response = ''
139139
for chunk in await self.decide(question, called_tools):
140-
response += chunk
140+
last_response += chunk
141141
yield {
142142
'is_task_complete': False,
143143
'require_user_input': False,
144144
'content': chunk,
145145
}
146-
tools = self.extract_tools(response)
146+
tools = self.extract_tools(last_response)
147147
if not tools:
148148
break
149149
results = await self.call_tool(tools)
150-
151150
called_tools += [
152-
{
153-
'tool': tool['name'],
154-
'arguments': tool['arguments'],
155-
'isError': result.isError,
156-
'result': result.content[0].text,
157-
}
151+
build_called_tool_record(tool, result)
158152
for tool, result in zip(tools, results, strict=True)
159153
]
160-
called_tools_history = called_tools_history_template.render(
161-
called_tools=called_tools, question=question
162-
)
163-
yield {
164-
'is_task_complete': False,
165-
'require_user_input': False,
166-
'content': called_tools_history,
167-
}
168154

169155
yield {
170156
'is_task_complete': True,
171157
'require_user_input': False,
172-
'content': 'Task completed',
158+
'content': last_response,
173159
}
174160

175161

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Previous tools have been called. {% for tool in called_tools %}
22
- Tool: {{ tool.name }}
33
- Arguments: {{ tool.arguments }}
4+
- isError: {{ tool.isError }}
45
- Result: {{ tool.result }}
56
{% endfor %}

samples/python/agents/a2a-mcp-without-framework/tests/__init__.py

Whitespace-only changes.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# ruff: noqa: S101
2+
import asyncio
3+
import sys
4+
5+
from pathlib import Path
6+
from types import SimpleNamespace
7+
8+
9+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'src'))
10+
11+
from no_llm_framework.server.agent import (
12+
Agent,
13+
build_called_tool_record,
14+
called_tools_history_template,
15+
)
16+
17+
18+
DECIDE_TURNS = 2
19+
20+
21+
def test_history_template_renders_name_from_builder() -> None:
22+
tool = {
23+
'name': 'fetch_A2A_documentation',
24+
'arguments': {'query': 'A2A'},
25+
}
26+
result = SimpleNamespace(
27+
isError=False,
28+
content=[SimpleNamespace(text='protocol overview')],
29+
)
30+
31+
record = build_called_tool_record(tool, result)
32+
rendered = called_tools_history_template.render(called_tools=[record])
33+
34+
assert record['name'] == 'fetch_A2A_documentation'
35+
assert 'tool' not in record
36+
assert '- Tool: fetch_A2A_documentation' in rendered
37+
assert '- isError: False' in rendered
38+
assert 'protocol overview' in rendered
39+
40+
41+
def test_stream_complete_event_uses_model_answer() -> None:
42+
tool_call = '```json\n[{"name": "fetch_A2A_documentation", "arguments": {"query": "A2A"}}]\n```'
43+
model_answer = '<Answer>\nA2A is an agent-to-agent protocol.\n</Answer>'
44+
decide_history: list[list[dict]] = []
45+
46+
async def fake_decide(question: str, called_tools: list[dict] | None = None) -> object:
47+
del question
48+
decide_history.append(list(called_tools or []))
49+
if not called_tools:
50+
return iter([tool_call])
51+
return iter([model_answer])
52+
53+
async def fake_call_tool(tools: list[dict]) -> list[SimpleNamespace]:
54+
del tools
55+
return [
56+
SimpleNamespace(
57+
isError=False,
58+
content=[SimpleNamespace(text='docs')],
59+
)
60+
]
61+
62+
async def collect_events() -> list[dict]:
63+
agent = Agent(mcp_url='https://example.test/mcp')
64+
agent.decide = fake_decide
65+
agent.call_tool = fake_call_tool
66+
return [event async for event in agent.stream('What is A2A?')]
67+
68+
events = asyncio.run(collect_events())
69+
complete_events = [event for event in events if event['is_task_complete']]
70+
contents = [event['content'] for event in events]
71+
72+
assert len(complete_events) == 1
73+
assert complete_events[0]['content'] == model_answer
74+
assert complete_events[0]['content'] != 'Task completed'
75+
assert not any(content.startswith('Step ') for content in contents)
76+
assert 'Previous tools have been called.' not in contents
77+
assert len(decide_history) == DECIDE_TURNS
78+
assert decide_history[1][0]['name'] == 'fetch_A2A_documentation'

0 commit comments

Comments
 (0)