Skip to content

Commit 7554bf8

Browse files
raychen911weimch
authored andcommitted
bugfix: 支持mcp工具多结果
1 parent abf32ad commit 7554bf8

5 files changed

Lines changed: 56 additions & 47 deletions

File tree

examples/mcp_tools/agent/tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def __init__(self):
7474
super().__init__()
7575
self._connection_params = StreamableHTTPConnectionParams(
7676
url="http://localhost:8000/mcp",
77-
headers={"Authorization": "Bearer token"},
77+
headers={"Authorization": "Bearer <token>"},
7878
timeout=5,
7979
sse_read_timeout=60 * 5,
8080
terminate_on_close=True, # send termination signal when the toolset is closed

examples/session_summarizer/run_agent.py

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ async def summarize_session(session_service: InMemorySessionService, app_name: s
127127
print(f" - Compression ratio: {summary.get_compression_ratio():.1f}%")
128128

129129

130-
SUMMARIZER_COUNT = 3 # Run summarization every SUMMARIZER_COUNT turns (e.g. 3 => every 3 turns)
130+
SUMMARIZER_COUNT = 2 # Keep the example short: summarize after a couple of turns.
131131

132132

133133
def create_summarizer_manager(model: OpenAIModel) -> SummarizerSessionManager:
@@ -154,8 +154,8 @@ def create_summarizer_manager(model: OpenAIModel) -> SummarizerSessionManager:
154154
# set_summarizer_time_interval_threshold(10),
155155
# )
156156
],
157-
max_summary_length=600, # Max summary length kept; default 1000; beyond shows ...
158-
keep_recent_count=4, # How many recent turns to keep; default 10
157+
max_summary_length=300, # Max summary length kept; default 1000; beyond shows ...
158+
keep_recent_count=2, # Keep only the latest turns so compression is easy to observe.
159159
)
160160
# Create SummarizerSessionManager
161161
summarizer_manager = SummarizerSessionManager(
@@ -169,7 +169,7 @@ def create_summarizer_manager(model: OpenAIModel) -> SummarizerSessionManager:
169169
async def llm_agent_summarizer():
170170
"""Demo LlmAgent integrated with SummarizerSessionManager."""
171171
print("=" * 60)
172-
print("Example 2: LlmAgent + SummarizerSessionManager demo")
172+
print("Example: LlmAgent + SummarizerSessionManager demo")
173173
print("=" * 60)
174174
app_name = "llm_summarizer_manager_demo"
175175

@@ -183,22 +183,13 @@ async def llm_agent_summarizer():
183183
current_session_id = str(uuid.uuid4())
184184
print(f"📊 Session: {app_name}/{user_id}/{current_session_id}")
185185

186-
# Demo conversation turns
186+
# Short demo conversation. Four turns are enough to trigger automatic
187+
# summarization while keeping the example quick to run.
187188
conversations = [
188189
"Hello! I want to learn Python programming. Can you help me?",
189190
"What is a variable? Can you give an example?",
190-
"Got it! What data types are there?",
191-
"What does control flow mean?",
192-
"I understand those ideas. I'd like a small project to practice.",
193-
"OK! How do I build this calculator?",
194-
"The calculator looks good—I ran it successfully. I'd like to learn more advanced Python.",
195-
"I'd like to start with functions—I think they're central to programming.",
196-
"I see—functions make code modular and reusable. I'd like to learn OOP next.",
197-
"I get OOP now. I'd like to learn exception handling.",
198-
"I've learned these advanced topics. I'd like a bigger project that ties them together.",
199-
"Yes! How do I implement this library system?",
200-
"The structure looks good. How do I persist data to files?",
201-
"Great! I've covered basics and advanced topics including files. I'd like a recap of what I learned.",
191+
"Please give me a tiny calculator example.",
192+
"Can you recap what I learned so far?",
202193
]
203194

204195
print(f"\n💬 Multi-turn dialogue ({len(conversations)} turns)...")
@@ -230,18 +221,18 @@ async def llm_agent_summarizer():
230221
# elif part.text:
231222
# print(f"\n✅ {part.text}")
232223

233-
# After every SUMMARIZER_COUNT turns, inspect session state
234-
if index % SUMMARIZER_COUNT == 0: # summarizer should fire around this cadence
235-
if session:
236-
print(f"\n📊 Session state after turn {index + 1}:")
237-
summary = await session_service.summarizer_manager.get_session_summary(session)
224+
# Inspect the summary after the threshold cadence.
225+
if (index + 1) % SUMMARIZER_COUNT == 0 and session:
226+
print(f"\n📊 Session state after turn {index + 1}:")
227+
summary = await session_service.summarizer_manager.get_session_summary(session)
228+
if summary:
238229
print(f" - Summary text: {summary.summary_text[:100]}...")
239230
print(f" - Original event count: {summary.original_event_count}")
240231
print(f" - Compressed event count: {summary.compressed_event_count}")
241232
print(f" - Compression ratio: {summary.get_compression_ratio()}")
233+
else:
234+
print(" - Summary not created yet.")
242235
print("\n" + "-" * 40)
243-
# Manual forced summary test
244-
await summarize_session(session_service, app_name, user_id, current_session_id)
245236

246237

247238
if __name__ == "__main__":

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ knowledge = [
7676
]
7777

7878
a2a = [
79-
"a2a-sdk>=0.2.0",
79+
"a2a-sdk<1.0.0,>=0.3.22",
8080
"protobuf>=5.29.5",
8181
]
8282

8383
agent-claude = [
84-
"claude-agent-sdk>=0.1.3",
84+
"claude-agent-sdk>=0.1.3,<0.1.64",
8585
"cloudpickle>=2.0.0",
8686
]
8787

@@ -115,20 +115,19 @@ dev = [
115115
"langchain_community>=0.3.27",
116116
"langchain_huggingface>=0.1.0",
117117
"ag-ui-protocol>=0.1.8",
118-
"claude-agent-sdk>=0.1.3",
118+
"claude-agent-sdk>=0.1.3,<0.1.64",
119119
"cloudpickle>=2.0.0",
120120
"typer>=0.9.0",
121121
]
122122

123123
all = [
124-
"a2a-sdk>=0.2.0",
125124
"protobuf>=5.29.5",
126125
"numpy>=2.2.5",
127126
"langchain_community>=0.3.27",
128127
"langchain_huggingface>=0.1.0",
129128
"langchain_tavily",
130129
"ag-ui-protocol>=0.1.8",
131-
"claude-agent-sdk>=0.1.3",
130+
"claude-agent-sdk>=0.1.3,<0.1.64",
132131
"pytest",
133132
"pytest-asyncio",
134133
"rouge-score",
@@ -140,6 +139,7 @@ all = [
140139
"nanobot-ai>=0.1.4.post6",
141140
"aiofiles",
142141
"wecom-aibot-sdk-python>=0.1.5",
142+
"a2a-sdk<1.0.0,>=0.3.22",
143143
]
144144

145145
[project.scripts]

tests/tools/mcp_tool/test_mcp_tool.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ def test_resource_content_with_blob(self):
358358
)
359359
assert tool._parse_mcp_call_tool_result_to_str(result) == "blob_data"
360360

361-
def test_multiple_contents_returns_first_text(self):
361+
def test_multiple_contents_returns_list(self):
362362
tool = self._tool()
363363
result = CallToolResult(
364364
isError=False,
@@ -367,18 +367,18 @@ def test_multiple_contents_returns_first_text(self):
367367
TextContent(type="text", text="second"),
368368
],
369369
)
370-
assert tool._parse_mcp_call_tool_result_to_str(result) == "first"
370+
assert tool._parse_mcp_call_tool_result_to_str(result) == ["first", "second"]
371371

372372
def test_fallback_returns_raw_content(self):
373-
"""When no content type matches, raw content list is returned."""
373+
"""When no content type matches, stringified raw content is returned."""
374374
tool = self._tool()
375375
result = MagicMock()
376376
result.isError = False
377377
mock_content = MagicMock()
378378
mock_content.type = "unknown"
379379
result.content = [mock_content]
380380
ret = tool._parse_mcp_call_tool_result_to_str(result)
381-
assert ret == result.content
381+
assert ret == str(result.content)
382382

383383

384384
# ---------------------------------------------------------------------------

trpc_agent_sdk/tools/mcp_tool/_mcp_tool.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from __future__ import annotations
3131

3232
from typing import Optional
33+
from typing import Union
3334
from typing_extensions import override
3435

3536
from mcp.types import CallToolResult
@@ -180,28 +181,45 @@ def _get_declaration(self) -> FunctionDeclaration:
180181
)
181182
return function_decl
182183

183-
def _parse_mcp_call_tool_result_to_str(self, result: CallToolResult) -> str:
184+
def _parse_mcp_call_tool_result_to_str(self, result: CallToolResult) -> Union[str, list[str]]:
184185
"""Converts MCP call result into standardized string format.
185186
186187
Args:
187188
result: Raw result from MCP tool call
188189
189190
Returns:
190-
str: Parsed result in string format
191+
Union[str, list[str]]: Parsed tool result.
192+
- Single parsed content returns ``str``.
193+
- Multiple parsed contents return ``list[str]``.
191194
"""
192-
if result.isError:
193-
return f"Error: {result.content[0].text}" # type: ignore
195+
parsed_items: list[str] = []
194196
for data in result.content:
195197
if data.type == "text":
196-
return data.text
197-
if data.type == "image":
198-
return data.data
199-
if data.type == "resource":
200-
text = getattr(data.resource, 'text', '')
201-
if not text:
202-
text = getattr(data.resource, 'blob', '')
203-
return text
204-
return result.content # type: ignore
198+
text = getattr(data, "text", "")
199+
if text:
200+
parsed_items.append(text)
201+
elif data.type == "image":
202+
image_data = getattr(data, "data", "")
203+
if image_data:
204+
parsed_items.append(image_data)
205+
elif data.type == "resource":
206+
resource = getattr(data, "resource", None)
207+
if resource is not None:
208+
text = getattr(resource, "text", "") or getattr(resource, "blob", "")
209+
if text:
210+
parsed_items.append(text)
211+
212+
if not parsed_items:
213+
fallback = str(result.content)
214+
return f"Error: {fallback}" if result.isError else fallback
215+
216+
if len(parsed_items) == 1:
217+
payload = parsed_items[0]
218+
return f"Error: {payload}" if result.isError else payload
219+
220+
if result.isError:
221+
return [f"Error: {item}" for item in parsed_items]
222+
return parsed_items
205223

206224
@retry_on_closed_resource
207225
@override

0 commit comments

Comments
 (0)