feat: 重构MCP功能和AI服务提供者架构
This commit is contained in:
@@ -71,7 +71,18 @@ class AnthropicClient:
|
||||
temperature: float,
|
||||
max_tokens: int,
|
||||
system_prompt: Optional[str] = None,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
tools: Optional[list] = None,
|
||||
tool_choice: Optional[str] = None,
|
||||
) -> AsyncGenerator[Dict[str, Any], None]:
|
||||
"""
|
||||
流式生成,支持工具调用
|
||||
|
||||
Yields:
|
||||
Dict with keys:
|
||||
- content: str - 文本内容块
|
||||
- tool_calls: list - 工具调用列表(如果有)
|
||||
- done: bool - 是否结束
|
||||
"""
|
||||
kwargs = {
|
||||
"model": model,
|
||||
"max_tokens": max_tokens,
|
||||
@@ -80,12 +91,42 @@ class AnthropicClient:
|
||||
}
|
||||
if system_prompt:
|
||||
kwargs["system"] = system_prompt
|
||||
if tools:
|
||||
kwargs["tools"] = tools
|
||||
if tool_choice == "required":
|
||||
kwargs["tool_choice"] = {"type": "any"}
|
||||
elif tool_choice == "auto":
|
||||
kwargs["tool_choice"] = {"type": "auto"}
|
||||
|
||||
try:
|
||||
async with self.client.messages.stream(**kwargs) as stream:
|
||||
try:
|
||||
async for text in stream.text_stream:
|
||||
yield text
|
||||
tool_calls = []
|
||||
async for chunk in stream:
|
||||
# 处理不同类型的块
|
||||
if chunk.type == "text_delta":
|
||||
yield {"content": chunk.text}
|
||||
elif chunk.type == "tool_use_delta":
|
||||
# 工具调用增量
|
||||
if not tool_calls or tool_calls[-1].get("id") != chunk.id:
|
||||
tool_calls.append({
|
||||
"id": chunk.id,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": chunk.name,
|
||||
"arguments": ""
|
||||
}
|
||||
})
|
||||
# 追加参数
|
||||
if tool_calls[-1]["function"]["arguments"] is None:
|
||||
tool_calls[-1]["function"]["arguments"] = ""
|
||||
tool_calls[-1]["function"]["arguments"] += chunk.input_gets_new_text or ""
|
||||
elif chunk.type == "message_delta":
|
||||
if chunk.stop_reason:
|
||||
# 流结束
|
||||
if tool_calls:
|
||||
yield {"tool_calls": tool_calls}
|
||||
yield {"done": True, "finish_reason": chunk.stop_reason}
|
||||
except GeneratorExit:
|
||||
# 生成器被关闭,这是正常的清理过程
|
||||
logger.debug("Anthropic 流式响应生成器被关闭(GeneratorExit)")
|
||||
|
||||
@@ -111,7 +111,18 @@ class GeminiClient:
|
||||
temperature: float,
|
||||
max_tokens: int,
|
||||
system_prompt: Optional[str] = None,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
tools: Optional[list] = None,
|
||||
tool_choice: Optional[str] = None,
|
||||
) -> AsyncGenerator[Dict[str, Any], None]:
|
||||
"""
|
||||
流式生成,支持工具调用
|
||||
|
||||
Yields:
|
||||
Dict with keys:
|
||||
- content: str - 文本内容块
|
||||
- tool_calls: list - 工具调用列表(如果有)
|
||||
- done: bool - 是否结束
|
||||
"""
|
||||
url = f"{self.base_url}/models/{model}:streamGenerateContent?key={self.api_key}&alt=sse"
|
||||
|
||||
contents = []
|
||||
@@ -125,6 +136,8 @@ class GeminiClient:
|
||||
}
|
||||
if system_prompt:
|
||||
payload["systemInstruction"] = {"parts": [{"text": system_prompt}]}
|
||||
if tools:
|
||||
payload["tools"] = self._convert_tools_to_gemini(tools)
|
||||
|
||||
try:
|
||||
async with self.client.stream("POST", url, json=payload) as response:
|
||||
@@ -139,9 +152,26 @@ class GeminiClient:
|
||||
if candidates and len(candidates) > 0:
|
||||
parts = candidates[0].get("content", {}).get("parts", [])
|
||||
if parts and len(parts) > 0:
|
||||
text = parts[0].get("text", "")
|
||||
text = ""
|
||||
function_calls = []
|
||||
for part in parts:
|
||||
if "text" in part:
|
||||
text += part["text"]
|
||||
elif "functionCall" in part:
|
||||
fc = part["functionCall"]
|
||||
function_calls.append({
|
||||
"id": f"call_{fc['name']}",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": fc["name"],
|
||||
"arguments": fc.get("args", {})
|
||||
}
|
||||
})
|
||||
|
||||
if text:
|
||||
yield text
|
||||
yield {"content": text}
|
||||
if function_calls:
|
||||
yield {"tool_calls": function_calls}
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except GeneratorExit:
|
||||
|
||||
@@ -86,8 +86,21 @@ class OpenAIClient(BaseAIClient):
|
||||
model: str,
|
||||
temperature: float,
|
||||
max_tokens: int,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
payload = self._build_payload(messages, model, temperature, max_tokens, stream=True)
|
||||
tools: Optional[list] = None,
|
||||
tool_choice: Optional[str] = None,
|
||||
) -> AsyncGenerator[Dict[str, Any], None]:
|
||||
"""
|
||||
流式生成,支持工具调用
|
||||
|
||||
Yields:
|
||||
Dict with keys:
|
||||
- content: str - 文本内容块
|
||||
- tool_calls: list - 工具调用列表(如果有)
|
||||
- done: bool - 是否结束
|
||||
"""
|
||||
payload = self._build_payload(messages, model, temperature, max_tokens, tools, tool_choice, stream=True)
|
||||
|
||||
tool_calls_buffer = {} # 收集工具调用块
|
||||
|
||||
try:
|
||||
async with await self._request_with_retry("POST", "/chat/completions", payload, stream=True) as response:
|
||||
@@ -97,14 +110,38 @@ class OpenAIClient(BaseAIClient):
|
||||
if line.startswith("data: "):
|
||||
data_str = line[6:]
|
||||
if data_str.strip() == "[DONE]":
|
||||
# 流结束,检查是否有工具调用需要处理
|
||||
if tool_calls_buffer:
|
||||
yield {"tool_calls": list(tool_calls_buffer.values()), "done": True}
|
||||
yield {"done": True}
|
||||
break
|
||||
try:
|
||||
data = json.loads(data_str)
|
||||
choices = data.get("choices", [])
|
||||
if choices and len(choices) > 0:
|
||||
content = choices[0].get("delta", {}).get("content", "")
|
||||
delta = choices[0].get("delta", {})
|
||||
content = delta.get("content", "")
|
||||
|
||||
# 检查工具调用
|
||||
tc_list = delta.get("tool_calls")
|
||||
if tc_list:
|
||||
for tc in tc_list:
|
||||
index = tc.get("index", 0)
|
||||
if index not in tool_calls_buffer:
|
||||
tool_calls_buffer[index] = tc
|
||||
else:
|
||||
existing = tool_calls_buffer[index]
|
||||
# 合并 function.arguments
|
||||
if "function" in tc and "function" in existing:
|
||||
if tc["function"].get("arguments"):
|
||||
existing["function"]["arguments"] = (
|
||||
existing["function"].get("arguments", "") +
|
||||
tc["function"]["arguments"]
|
||||
)
|
||||
|
||||
if content:
|
||||
yield content
|
||||
yield {"content": content}
|
||||
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except GeneratorExit:
|
||||
|
||||
Reference in New Issue
Block a user