ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
@@ -0,0 +1,335 @@
import logging
import json
import traceback
from aworld.core.context.base import Context
# from fastmcp.server.middleware import Middleware, MiddlewareContext
from aworld.utils.common import sync_exec
from aworld.events.util import send_message
from aworld.core.event.base import Message, Constants
from typing_extensions import Optional, List, Dict, Any
from aworld.mcp_client.utils import mcp_tool_desc_transform, call_api, get_server_instance, cleanup_server, \
call_function_tool, mcp_tool_desc_transform_v2
from mcp.types import TextContent, ImageContent
from aworld.core.common import ActionResult
from aworld.output import Output
class McpServers:
def __init__(
self,
mcp_servers: Optional[List[str]] = None,
mcp_config: Dict[str, Any] = None,
sandbox=None,
black_tool_actions: Dict[str, List[str]] = None,
) -> None:
self.mcp_servers = mcp_servers
self.mcp_config = mcp_config
self.sandbox = sandbox
# Dictionary to store server instances {server_name: server_instance}
self.server_instances = {}
self.tool_list = None
self.black_tool_actions = black_tool_actions or {}
async def list_tools(self, context: Context = None) -> List[Dict[str, Any]]:
if self.tool_list:
return self.tool_list
if not self.mcp_servers or not self.mcp_config:
return []
try:
#self.tool_list = await mcp_tool_desc_transform(self.mcp_servers, self.mcp_config)
self.tool_list = await mcp_tool_desc_transform_v2(self.mcp_servers, self.mcp_config,context,self.server_instances,self.black_tool_actions)
return self.tool_list
except Exception as e:
traceback.print_exc()
logging.warning(f"Failed to list tools: {e}")
return []
async def check_tool_params(self, context: Context, server_name: str, tool_name: str,
parameter: Dict[str, Any]) -> Any:
"""
Check tool parameters and automatically supplement session_id, task_id and other parameters from context
Args:
context: Context object containing session_id, task_id and other information
server_name: Server name
tool_name: Tool name
parameter: Parameter dictionary, will be modified
Returns:
bool: Whether parameter check passed
"""
# Ensure tool_list is loaded
if not self.tool_list or not context:
return False
if not self.mcp_servers or not self.mcp_config:
return False
try:
# Build unique identifier for the tool
tool_identifier = f"mcp__{server_name}__{tool_name}"
# Find corresponding tool in tool_list
target_tool = None
for tool in self.tool_list:
if tool.get("type") == "function" and tool.get("function", {}).get("name") == tool_identifier:
target_tool = tool
break
if not target_tool:
logging.warning(f"Tool not found: {tool_identifier}")
return False
# Get tool parameter definitions
function_info = target_tool.get("function", {})
tool_parameters = function_info.get("parameters", {})
properties = tool_parameters.get("properties", {})
# Check if session_id or task_id parameters are needed
# Check if session_id is needed
if "session_id" in properties:
if hasattr(context, 'session_id') and context.session_id:
parameter["session_id"] = context.session_id
logging.info(f"Auto-added session_id: {context.session_id}")
# Check if task_id is needed
if "task_id" in properties:
if hasattr(context, 'task_id') and context.task_id:
parameter["task_id"] = context.task_id
logging.info(f"Auto-added task_id: {context.task_id}")
return True
except Exception as e:
logging.warning(f"Error checking tool parameters: {e}")
return False
async def call_tool(
self,
action_list: List[Dict[str, Any]] = None,
task_id: str = None,
session_id: str = None,
context: Context = None
) -> List[ActionResult]:
results = []
if not action_list:
return None
try:
for action in action_list:
if not isinstance(action, dict):
action_dict = vars(action)
else:
action_dict = action
# Get values from dictionary
server_name = action_dict.get("tool_name")
tool_name = action_dict.get("action_name")
parameter = action_dict.get("params", {})
result_key = f"{server_name}__{tool_name}"
operation_info = {
"server_name": server_name,
"tool_name": tool_name,
"params": parameter
}
if not server_name or not tool_name:
continue
# Check server type
server_type = None
if self.mcp_config and self.mcp_config.get("mcpServers"):
server_config = self.mcp_config.get("mcpServers").get(server_name, {})
server_type = server_config.get("type", "")
if server_type == "function_tool":
try:
call_result = await call_function_tool(
server_name, tool_name, parameter, self.mcp_config
)
results.append(call_result)
self._update_metadata(result_key, call_result, operation_info)
except Exception as e:
logging.warning(f"Error calling function_tool tool: {e}")
self._update_metadata(result_key, {"error": str(e)}, operation_info)
continue
# For API type servers, use call_api function directly
if server_type == "api":
try:
call_result = await call_api(
server_name, tool_name, parameter, self.mcp_config
)
results.append(call_result)
self._update_metadata(result_key, call_result, operation_info)
except Exception as e:
logging.warning(f"Error calling API tool: {e}")
self._update_metadata(result_key, {"error": str(e)}, operation_info)
continue
# Prioritize using existing server instances
server = self.server_instances.get(server_name)
if server is None:
# If it doesn't exist, create a new instance and save it
server = await get_server_instance(server_name, self.mcp_config,context)
if server:
self.server_instances[server_name] = server
logging.info(f"Created and cached new server instance for {server_name}")
else:
logging.warning(f"Created new server failed: {server_name}, session_id: {session_id}, tool_name: {tool_name}")
self._update_metadata(result_key, {"error": "Failed to create server instance"}, operation_info)
continue
# Use server instance to call the tool
call_result_raw = None
action_result = ActionResult(
tool_name=server_name,
action_name=tool_name,
content="",
keep=True
)
max_retry = 3
for i in range(max_retry):
try:
async def progress_callback(
progress: float, total: float | None, message: str | None
):
try:
output = Output()
output.data = message
tool_output_message = Message(
category=Constants.OUTPUT,
payload=output,
sender=f"{server_name}__{tool_name}",
session_id=context.session_id if context else "",
headers={"context": context}
)
sync_exec(send_message, tool_output_message)
except BaseException as e:
logging.warning(f"Error calling progress callback: {e}")
await self.check_tool_params(context=context, server_name=server_name, tool_name=tool_name,
parameter=parameter)
call_result_raw = await server.call_tool(tool_name=tool_name, arguments=parameter,
progress_callback=progress_callback)
break
except BaseException as e:
logging.warning(
f"Error calling tool error: {e}. Extra info: session_id = {session_id}, tool_name = {tool_name}."
f"Traceback:\n{traceback.format_exc()}"
)
logging.info(f"tool_name:{server_name},action_name:{tool_name} finished.")
logging.debug(f"tool_name:{server_name},action_name:{tool_name} call-mcp-tool-result: {call_result_raw}")
if not call_result_raw:
logging.warning(f"Error calling tool with cached server")
self._update_metadata(result_key, {"error": str(e)}, operation_info)
# If using cached server instance fails, try to clean up and recreate
if server_name in self.server_instances:
try:
await cleanup_server(self.server_instances[server_name])
del self.server_instances[server_name]
except Exception as e:
logging.warning(f"Failed to cleanup server {server_name}: {e}")
else:
if call_result_raw and call_result_raw.content:
metadata = call_result_raw.content[0].model_extra.get("metadata", {})
artifact_datas = []
content_list: list[str] = []
for content in call_result_raw.content:
if isinstance(call_result_raw.content[0], TextContent):
content_list.append(content.text)
_metadata = content.model_extra.get("metadata", {})
if "artifact_data" in _metadata and isinstance(_metadata["artifact_data"], dict):
artifact_datas.append({
"artifact_type": _metadata["artifact_type"],
"artifact_data": _metadata["artifact_data"]
})
elif isinstance(call_result_raw.content[0], ImageContent):
content_list.append(f"data:image/jpeg;base64,{content.data}")
_metadata = content.model_extra.get("metadata", {})
if "artifact_data" in _metadata and isinstance(_metadata["artifact_data"], dict):
artifact_datas.append({
"artifact_type": _metadata["artifact_type"],
"artifact_data": _metadata["artifact_data"]
})
if metadata and artifact_datas:
metadata["artifacts"] = artifact_datas
action_result = ActionResult(
tool_name=server_name,
action_name=tool_name,
content=json.dumps(content_list, ensure_ascii=False),
keep=True,
metadata=metadata,
parameter=parameter
)
results.append(action_result)
self._update_metadata(result_key, action_result, operation_info)
except Exception as e:
logging.warning(f"Failed to call_tool: {e}.Extra info: session_id = {session_id}, action_list = {action_list}")
return None
return results
def _update_metadata(self, result_key: str, result: Any, operation_info: Dict[str, Any]):
"""
Update sandbox metadata with a single tool call result
Args:
result_key: The key name in metadata
result: Tool call result
operation_info: Operation information
"""
if not self.sandbox or not hasattr(self.sandbox, '_metadata'):
return
try:
metadata = self.sandbox._metadata.get("mcp_metadata", {})
tmp_data = {
"input": operation_info,
"output": result
}
if not metadata:
metadata["mcp_metadata"] = {}
metadata["mcp_metadata"][result_key] = [tmp_data]
self.sandbox._metadata["mcp_metadata"] = metadata
return
_metadata = metadata.get(result_key, [])
if not _metadata:
_metadata[result_key] = [_metadata]
else:
_metadata[result_key].append(tmp_data)
metadata[result_key] = _metadata
self.sandbox._metadata["mcp_metadata"] = metadata
return
except Exception as e:
logging.debug(f"Failed to update sandbox metadata: {e}")
# Add cleanup method, called when Sandbox is destroyed
async def cleanup(self):
"""Clean up all server connections"""
for server_name, server in list(self.server_instances.items()):
try:
await cleanup_server(server)
del self.server_instances[server_name]
logging.info(f"Cleaned up server instance for {server_name}")
except Exception as e:
logging.warning(f"Failed to cleanup server {server_name}: {e}")