Files
MuMuAINovel/backend/app/mcp/adapters/function_calling.py
T
xiamuceer 69e3e46c96 feat: 优化MCP工具调用体验并集成通用适配器
- 静默检查MCP工具可用性,支持提示词注入调用mcp
- 集成UniversalMCPAdapter,支持自动API能力检测和智能降级
- 新增MCP适配器配置项,增强系统兼容性和健壮性
2025-11-24 11:30:27 +08:00

171 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Function Calling适配器 - 支持原生Function Calling的API"""
import json
from typing import Dict, Any, List
from app.mcp.adapters.base import BaseMCPAdapter, AdapterType, ToolCallResult
from app.logger import get_logger
logger = get_logger(__name__)
class FunctionCallingAdapter(BaseMCPAdapter):
"""Function Calling适配器 - 用于支持原生工具调用的AI API(如OpenAI"""
def __init__(self):
super().__init__()
self.adapter_type = AdapterType.FUNCTION_CALLING
def supports_native_tools(self) -> bool:
"""支持原生工具调用"""
return True
def format_tools_for_prompt(
self,
tools: List[Dict[str, Any]],
user_message: str
) -> str:
"""
Function Calling模式下,工具通过API参数传递,不需要修改提示词
Returns:
原始用户消息
"""
return user_message
def get_tools_for_api(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
获取适用于API的工具格式
Args:
tools: MCP工具列表
Returns:
适用于OpenAI Function Calling的工具格式
"""
return tools
def parse_tool_calls(self, ai_response: Any) -> ToolCallResult:
"""
从AI响应中解析工具调用(Function Calling格式)
Args:
ai_response: AI响应对象(通常是OpenAI的ChatCompletion对象)
Returns:
解析结果
"""
try:
# 处理不同类型的响应
if isinstance(ai_response, dict):
# 字典格式(OpenAI API响应)
message = ai_response.get("choices", [{}])[0].get("message", {})
tool_calls = message.get("tool_calls", [])
content = message.get("content", "")
elif hasattr(ai_response, "choices"):
# 对象格式(OpenAI SDK响应)
message = ai_response.choices[0].message
tool_calls = getattr(message, "tool_calls", None) or []
content = getattr(message, "content", "") or ""
# 转换为字典格式
if tool_calls:
tool_calls = [
{
"id": tc.id,
"type": tc.type,
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments
}
}
for tc in tool_calls
]
else:
# 字符串格式(降级为文本响应)
return ToolCallResult(
tool_calls=[],
raw_response=str(ai_response),
has_tool_calls=False
)
has_tool_calls = len(tool_calls) > 0
if has_tool_calls:
logger.info(f"✅ Function Calling模式解析出 {len(tool_calls)} 个工具调用")
for tc in tool_calls:
logger.info(f" - {tc['function']['name']}")
return ToolCallResult(
tool_calls=tool_calls,
raw_response=content or "",
has_tool_calls=has_tool_calls,
needs_continuation=has_tool_calls
)
except Exception as e:
logger.error(f"❌ 解析Function Calling响应失败: {e}", exc_info=True)
return ToolCallResult(
tool_calls=[],
raw_response=str(ai_response),
has_tool_calls=False
)
def build_continuation_prompt(
self,
original_message: str,
ai_response: str,
tool_results: List[Dict[str, Any]]
) -> str:
"""
构建包含工具结果的继续对话提示词
在Function Calling模式下,这通常不需要,因为工具结果会作为消息历史的一部分
"""
# Function Calling模式下通常通过消息历史传递工具结果
# 这里提供一个降级方案
results_text = "\n\n".join([
f"工具 {r['name']} 的结果:\n{r['content']}"
for r in tool_results
])
return f"{original_message}\n\n工具执行结果:\n{results_text}\n\n请基于以上工具结果回答用户的问题。"
def build_messages_with_tool_results(
self,
messages: List[Dict[str, Any]],
tool_calls: List[Dict[str, Any]],
tool_results: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""
构建包含工具结果的消息历史(Function Calling标准格式)
Args:
messages: 原始消息历史
tool_calls: AI的工具调用
tool_results: 工具执行结果
Returns:
更新后的消息历史
"""
new_messages = messages.copy()
# 添加助手的工具调用消息
new_messages.append({
"role": "assistant",
"content": None,
"tool_calls": tool_calls
})
# 添加工具结果消息
for result in tool_results:
new_messages.append({
"role": "tool",
"tool_call_id": result.get("tool_call_id", ""),
"name": result.get("name", ""),
"content": result.get("content", "")
})
return new_messages