mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-17 10:10:00 +08:00
✨ feat(llm): 全面重构LLM服务模块,增强多模态与工具支持 (#1953)
* ✨ feat(llm): 全面重构LLM服务模块,增强多模态与工具支持 🚀 核心功能增强 - 多模型链式调用:新增 `pipeline_chat` 支持复杂任务流处理 - 扩展提供商支持:新增 ARK(火山方舟)、SiliconFlow(硅基流动) 适配器 - 多模态处理增强:支持URL媒体文件下载转换,提升输入灵活性 - 历史对话支持:AI.analyze 方法支持历史消息上下文和可选 UniMessage 参数 - 文本嵌入功能:新增 `embed`、`analyze_multimodal`、`search_multimodal` 等API - 模型能力系统:新增 `ModelCapabilities` 统一管理模型特性(多模态、工具调用等) 🔧 架构重构与优化 - MCP工具系统重构:配置独立化至 `data/llm/mcp_tools.json`,预置常用工具 - API调用逻辑统一:提取通用 `_perform_api_call` 方法,消除代码重复 - 跨平台兼容:Windows平台MCP工具npx命令自动包装处理 - HTTP客户端增强:兼容不同版本httpx代理配置(0.28+版本适配) 🛠️ API与配置完善 - 统一返回类型:`AI.analyze` 统一返回 `LLMResponse` 类型 - 消息转换工具:新增 `message_to_unimessage` 转换函数 - Gemini适配器增强:URL图片下载编码、动态安全阈值配置 - 缓存管理:新增模型实例缓存和管理功能 - 配置预设:扩展 CommonOverrides 预设配置选项 - 历史管理优化:支持多模态内容占位符替换,提升效率 📚 文档与开发体验 - README全面重写:新增完整使用指南、API参考和架构概览 - 文档内容扩充:补充嵌入模型、缓存管理、工具注册等功能说明 - 日志记录增强:支持详细调试信息输出 - API简化:移除冗余函数,优化接口设计 * 🎨 feat(llm): 统一LLM服务函数文档格式 * ✨ feat(llm): 添加新模型并简化提供者配置加载 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
webjoin111
pre-commit-ci[bot]
parent
1e7ae38684
commit
48cbb2bf1d
@@ -8,7 +8,6 @@ from .base import BaseAdapter, OpenAICompatAdapter, RequestData, ResponseData
|
||||
from .factory import LLMAdapterFactory, get_adapter_for_api_type, register_adapter
|
||||
from .gemini import GeminiAdapter
|
||||
from .openai import OpenAIAdapter
|
||||
from .zhipu import ZhipuAdapter
|
||||
|
||||
LLMAdapterFactory.initialize()
|
||||
|
||||
@@ -20,7 +19,6 @@ __all__ = [
|
||||
"OpenAICompatAdapter",
|
||||
"RequestData",
|
||||
"ResponseData",
|
||||
"ZhipuAdapter",
|
||||
"get_adapter_for_api_type",
|
||||
"register_adapter",
|
||||
]
|
||||
|
||||
@@ -17,6 +17,7 @@ if TYPE_CHECKING:
|
||||
from ..service import LLMModel
|
||||
from ..types.content import LLMMessage
|
||||
from ..types.enums import EmbeddingTaskType
|
||||
from ..types.models import LLMTool
|
||||
|
||||
|
||||
class RequestData(BaseModel):
|
||||
@@ -60,7 +61,7 @@ class BaseAdapter(ABC):
|
||||
"""支持的API类型列表"""
|
||||
pass
|
||||
|
||||
def prepare_simple_request(
|
||||
async def prepare_simple_request(
|
||||
self,
|
||||
model: "LLMModel",
|
||||
api_key: str,
|
||||
@@ -86,7 +87,7 @@ class BaseAdapter(ABC):
|
||||
|
||||
config = model._generation_config
|
||||
|
||||
return self.prepare_advanced_request(
|
||||
return await self.prepare_advanced_request(
|
||||
model=model,
|
||||
api_key=api_key,
|
||||
messages=messages,
|
||||
@@ -96,13 +97,13 @@ class BaseAdapter(ABC):
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def prepare_advanced_request(
|
||||
async def prepare_advanced_request(
|
||||
self,
|
||||
model: "LLMModel",
|
||||
api_key: str,
|
||||
messages: list["LLMMessage"],
|
||||
config: "LLMGenerationConfig | None" = None,
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
tools: list["LLMTool"] | None = None,
|
||||
tool_choice: str | dict[str, Any] | None = None,
|
||||
) -> RequestData:
|
||||
"""准备高级请求"""
|
||||
@@ -238,6 +239,9 @@ class BaseAdapter(ABC):
|
||||
message = choice.get("message", {})
|
||||
content = message.get("content", "")
|
||||
|
||||
if content:
|
||||
content = content.strip()
|
||||
|
||||
parsed_tool_calls: list[LLMToolCall] | None = None
|
||||
if message_tool_calls := message.get("tool_calls"):
|
||||
from ..types.models import LLMToolFunction
|
||||
@@ -375,7 +379,7 @@ class BaseAdapter(ABC):
|
||||
if model.temperature is not None:
|
||||
base_config["temperature"] = model.temperature
|
||||
if model.max_tokens is not None:
|
||||
if model.api_type in ["gemini", "gemini_native"]:
|
||||
if model.api_type == "gemini":
|
||||
base_config["maxOutputTokens"] = model.max_tokens
|
||||
else:
|
||||
base_config["max_tokens"] = model.max_tokens
|
||||
@@ -401,26 +405,51 @@ class OpenAICompatAdapter(BaseAdapter):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_chat_endpoint(self) -> str:
|
||||
def get_chat_endpoint(self, model: "LLMModel") -> str:
|
||||
"""子类必须实现,返回 chat completions 的端点"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_embedding_endpoint(self) -> str:
|
||||
def get_embedding_endpoint(self, model: "LLMModel") -> str:
|
||||
"""子类必须实现,返回 embeddings 的端点"""
|
||||
pass
|
||||
|
||||
def prepare_advanced_request(
|
||||
async def prepare_simple_request(
|
||||
self,
|
||||
model: "LLMModel",
|
||||
api_key: str,
|
||||
prompt: str,
|
||||
history: list[dict[str, str]] | None = None,
|
||||
) -> RequestData:
|
||||
"""准备简单文本生成请求 - OpenAI兼容API的通用实现"""
|
||||
url = self.get_api_url(model, self.get_chat_endpoint(model))
|
||||
headers = self.get_base_headers(api_key)
|
||||
|
||||
messages = []
|
||||
if history:
|
||||
messages.extend(history)
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
body = {
|
||||
"model": model.model_name,
|
||||
"messages": messages,
|
||||
}
|
||||
|
||||
body = self.apply_config_override(model, body)
|
||||
|
||||
return RequestData(url=url, headers=headers, body=body)
|
||||
|
||||
async def prepare_advanced_request(
|
||||
self,
|
||||
model: "LLMModel",
|
||||
api_key: str,
|
||||
messages: list["LLMMessage"],
|
||||
config: "LLMGenerationConfig | None" = None,
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
tools: list["LLMTool"] | None = None,
|
||||
tool_choice: str | dict[str, Any] | None = None,
|
||||
) -> RequestData:
|
||||
"""准备高级请求 - OpenAI兼容格式"""
|
||||
url = self.get_api_url(model, self.get_chat_endpoint())
|
||||
url = self.get_api_url(model, self.get_chat_endpoint(model))
|
||||
headers = self.get_base_headers(api_key)
|
||||
openai_messages = self.convert_messages_to_openai_format(messages)
|
||||
|
||||
@@ -430,7 +459,21 @@ class OpenAICompatAdapter(BaseAdapter):
|
||||
}
|
||||
|
||||
if tools:
|
||||
body["tools"] = tools
|
||||
openai_tools = []
|
||||
for tool in tools:
|
||||
if tool.type == "function" and tool.function:
|
||||
openai_tools.append({"type": "function", "function": tool.function})
|
||||
elif tool.type == "mcp" and tool.mcp_session:
|
||||
if callable(tool.mcp_session):
|
||||
raise ValueError(
|
||||
"适配器接收到未激活的 MCP 会话工厂。"
|
||||
"会话工厂应该在 LLMModel.generate_response 中被激活。"
|
||||
)
|
||||
openai_tools.append(
|
||||
tool.mcp_session.to_api_tool(api_type=self.api_type)
|
||||
)
|
||||
if openai_tools:
|
||||
body["tools"] = openai_tools
|
||||
if tool_choice:
|
||||
body["tool_choice"] = tool_choice
|
||||
|
||||
@@ -444,7 +487,7 @@ class OpenAICompatAdapter(BaseAdapter):
|
||||
is_advanced: bool = False,
|
||||
) -> ResponseData:
|
||||
"""解析响应 - 直接使用基类的 OpenAI 格式解析"""
|
||||
_ = model, is_advanced # 未使用的参数
|
||||
_ = model, is_advanced
|
||||
return self.parse_openai_response(response_json)
|
||||
|
||||
def prepare_embedding_request(
|
||||
@@ -456,8 +499,8 @@ class OpenAICompatAdapter(BaseAdapter):
|
||||
**kwargs: Any,
|
||||
) -> RequestData:
|
||||
"""准备嵌入请求 - OpenAI兼容格式"""
|
||||
_ = task_type # 未使用的参数
|
||||
url = self.get_api_url(model, self.get_embedding_endpoint())
|
||||
_ = task_type
|
||||
url = self.get_api_url(model, self.get_embedding_endpoint(model))
|
||||
headers = self.get_base_headers(api_key)
|
||||
|
||||
body = {
|
||||
@@ -465,7 +508,6 @@ class OpenAICompatAdapter(BaseAdapter):
|
||||
"input": texts,
|
||||
}
|
||||
|
||||
# 应用额外的配置参数
|
||||
if kwargs:
|
||||
body.update(kwargs)
|
||||
|
||||
|
||||
@@ -22,10 +22,8 @@ class LLMAdapterFactory:
|
||||
|
||||
from .gemini import GeminiAdapter
|
||||
from .openai import OpenAIAdapter
|
||||
from .zhipu import ZhipuAdapter
|
||||
|
||||
cls.register_adapter(OpenAIAdapter())
|
||||
cls.register_adapter(ZhipuAdapter())
|
||||
cls.register_adapter(GeminiAdapter())
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
||||
from ..service import LLMModel
|
||||
from ..types.content import LLMMessage
|
||||
from ..types.enums import EmbeddingTaskType
|
||||
from ..types.models import LLMToolCall
|
||||
from ..types.models import LLMTool, LLMToolCall
|
||||
|
||||
|
||||
class GeminiAdapter(BaseAdapter):
|
||||
@@ -38,30 +38,16 @@ class GeminiAdapter(BaseAdapter):
|
||||
|
||||
return headers
|
||||
|
||||
def prepare_advanced_request(
|
||||
async def prepare_advanced_request(
|
||||
self,
|
||||
model: "LLMModel",
|
||||
api_key: str,
|
||||
messages: list["LLMMessage"],
|
||||
config: "LLMGenerationConfig | None" = None,
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
tools: list["LLMTool"] | None = None,
|
||||
tool_choice: str | dict[str, Any] | None = None,
|
||||
) -> RequestData:
|
||||
"""准备高级请求"""
|
||||
return self._prepare_request(
|
||||
model, api_key, messages, config, tools, tool_choice
|
||||
)
|
||||
|
||||
def _prepare_request(
|
||||
self,
|
||||
model: "LLMModel",
|
||||
api_key: str,
|
||||
messages: list["LLMMessage"],
|
||||
config: "LLMGenerationConfig | None" = None,
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
tool_choice: str | dict[str, Any] | None = None,
|
||||
) -> RequestData:
|
||||
"""准备 Gemini API 请求 - 支持所有高级功能"""
|
||||
effective_config = config if config is not None else model._generation_config
|
||||
|
||||
endpoint = self._get_gemini_endpoint(model, effective_config)
|
||||
@@ -78,7 +64,8 @@ class GeminiAdapter(BaseAdapter):
|
||||
system_instruction_parts = [{"text": msg.content}]
|
||||
elif isinstance(msg.content, list):
|
||||
system_instruction_parts = [
|
||||
part.convert_for_api("gemini") for part in msg.content
|
||||
await part.convert_for_api_async("gemini")
|
||||
for part in msg.content
|
||||
]
|
||||
continue
|
||||
|
||||
@@ -87,7 +74,9 @@ class GeminiAdapter(BaseAdapter):
|
||||
current_parts.append({"text": msg.content})
|
||||
elif isinstance(msg.content, list):
|
||||
for part_obj in msg.content:
|
||||
current_parts.append(part_obj.convert_for_api("gemini"))
|
||||
current_parts.append(
|
||||
await part_obj.convert_for_api_async("gemini")
|
||||
)
|
||||
gemini_contents.append({"role": "user", "parts": current_parts})
|
||||
|
||||
elif msg.role == "assistant" or msg.role == "model":
|
||||
@@ -95,7 +84,9 @@ class GeminiAdapter(BaseAdapter):
|
||||
current_parts.append({"text": msg.content})
|
||||
elif isinstance(msg.content, list):
|
||||
for part_obj in msg.content:
|
||||
current_parts.append(part_obj.convert_for_api("gemini"))
|
||||
current_parts.append(
|
||||
await part_obj.convert_for_api_async("gemini")
|
||||
)
|
||||
|
||||
if msg.tool_calls:
|
||||
import json
|
||||
@@ -154,16 +145,22 @@ class GeminiAdapter(BaseAdapter):
|
||||
|
||||
all_tools_for_request = []
|
||||
if tools:
|
||||
for tool_item in tools:
|
||||
if isinstance(tool_item, dict):
|
||||
if "name" in tool_item and "description" in tool_item:
|
||||
all_tools_for_request.append(
|
||||
{"functionDeclarations": [tool_item]}
|
||||
for tool in tools:
|
||||
if tool.type == "function" and tool.function:
|
||||
all_tools_for_request.append(
|
||||
{"functionDeclarations": [tool.function]}
|
||||
)
|
||||
elif tool.type == "mcp" and tool.mcp_session:
|
||||
if callable(tool.mcp_session):
|
||||
raise ValueError(
|
||||
"适配器接收到未激活的 MCP 会话工厂。"
|
||||
"会话工厂应该在 LLMModel.generate_response 中被激活。"
|
||||
)
|
||||
else:
|
||||
all_tools_for_request.append(tool_item)
|
||||
else:
|
||||
all_tools_for_request.append(tool_item)
|
||||
all_tools_for_request.append(
|
||||
tool.mcp_session.to_api_tool(api_type=self.api_type)
|
||||
)
|
||||
elif tool.type == "google_search":
|
||||
all_tools_for_request.append({"googleSearch": {}})
|
||||
|
||||
if effective_config:
|
||||
if getattr(effective_config, "enable_grounding", False):
|
||||
@@ -183,11 +180,7 @@ class GeminiAdapter(BaseAdapter):
|
||||
logger.debug("隐式启用代码执行工具。")
|
||||
|
||||
if all_tools_for_request:
|
||||
gemini_api_tools = self._convert_tools_to_gemini_format(
|
||||
all_tools_for_request
|
||||
)
|
||||
if gemini_api_tools:
|
||||
body["tools"] = gemini_api_tools
|
||||
body["tools"] = all_tools_for_request
|
||||
|
||||
final_tool_choice = tool_choice
|
||||
if final_tool_choice is None and effective_config:
|
||||
@@ -241,38 +234,6 @@ class GeminiAdapter(BaseAdapter):
|
||||
|
||||
return f"/v1beta/models/{model.model_name}:generateContent"
|
||||
|
||||
def _convert_tools_to_gemini_format(
|
||||
self, tools: list[dict[str, Any]]
|
||||
) -> list[dict[str, Any]]:
|
||||
"""转换工具格式为Gemini格式"""
|
||||
gemini_tools = []
|
||||
|
||||
for tool in tools:
|
||||
if tool.get("type") == "function":
|
||||
func = tool["function"]
|
||||
gemini_tool = {
|
||||
"functionDeclarations": [
|
||||
{
|
||||
"name": func["name"],
|
||||
"description": func.get("description", ""),
|
||||
"parameters": func.get("parameters", {}),
|
||||
}
|
||||
]
|
||||
}
|
||||
gemini_tools.append(gemini_tool)
|
||||
elif tool.get("type") == "code_execution":
|
||||
gemini_tools.append(
|
||||
{"codeExecution": {"language": tool.get("language", "python")}}
|
||||
)
|
||||
elif tool.get("type") == "google_search":
|
||||
gemini_tools.append({"googleSearch": {}})
|
||||
elif "googleSearch" in tool:
|
||||
gemini_tools.append({"googleSearch": tool["googleSearch"]})
|
||||
elif "codeExecution" in tool:
|
||||
gemini_tools.append({"codeExecution": tool["codeExecution"]})
|
||||
|
||||
return gemini_tools
|
||||
|
||||
def _convert_tool_choice_to_gemini(
|
||||
self, tool_choice_value: str | dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
@@ -395,10 +356,11 @@ class GeminiAdapter(BaseAdapter):
|
||||
for category, threshold in custom_safety_settings.items():
|
||||
safety_settings.append({"category": category, "threshold": threshold})
|
||||
else:
|
||||
from ..config.providers import get_gemini_safety_threshold
|
||||
|
||||
threshold = get_gemini_safety_threshold()
|
||||
for category in safety_categories:
|
||||
safety_settings.append(
|
||||
{"category": category, "threshold": "BLOCK_MEDIUM_AND_ABOVE"}
|
||||
)
|
||||
safety_settings.append({"category": category, "threshold": threshold})
|
||||
|
||||
return safety_settings if safety_settings else None
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"""
|
||||
OpenAI API 适配器
|
||||
|
||||
支持 OpenAI、DeepSeek 和其他 OpenAI 兼容的 API 服务。
|
||||
支持 OpenAI、DeepSeek、智谱AI 和其他 OpenAI 兼容的 API 服务。
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import OpenAICompatAdapter, RequestData
|
||||
from .base import OpenAICompatAdapter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..service import LLMModel
|
||||
@@ -21,37 +21,18 @@ class OpenAIAdapter(OpenAICompatAdapter):
|
||||
|
||||
@property
|
||||
def supported_api_types(self) -> list[str]:
|
||||
return ["openai", "deepseek", "general_openai_compat"]
|
||||
return ["openai", "deepseek", "zhipu", "general_openai_compat", "ark"]
|
||||
|
||||
def get_chat_endpoint(self) -> str:
|
||||
def get_chat_endpoint(self, model: "LLMModel") -> str:
|
||||
"""返回聊天完成端点"""
|
||||
if model.api_type == "ark":
|
||||
return "/api/v3/chat/completions"
|
||||
if model.api_type == "zhipu":
|
||||
return "/api/paas/v4/chat/completions"
|
||||
return "/v1/chat/completions"
|
||||
|
||||
def get_embedding_endpoint(self) -> str:
|
||||
"""返回嵌入端点"""
|
||||
def get_embedding_endpoint(self, model: "LLMModel") -> str:
|
||||
"""根据API类型返回嵌入端点"""
|
||||
if model.api_type == "zhipu":
|
||||
return "/v4/embeddings"
|
||||
return "/v1/embeddings"
|
||||
|
||||
def prepare_simple_request(
|
||||
self,
|
||||
model: "LLMModel",
|
||||
api_key: str,
|
||||
prompt: str,
|
||||
history: list[dict[str, str]] | None = None,
|
||||
) -> RequestData:
|
||||
"""准备简单文本生成请求 - OpenAI优化实现"""
|
||||
url = self.get_api_url(model, self.get_chat_endpoint())
|
||||
headers = self.get_base_headers(api_key)
|
||||
|
||||
messages = []
|
||||
if history:
|
||||
messages.extend(history)
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
body = {
|
||||
"model": model.model_name,
|
||||
"messages": messages,
|
||||
}
|
||||
|
||||
body = self.apply_config_override(model, body)
|
||||
|
||||
return RequestData(url=url, headers=headers, body=body)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
"""
|
||||
智谱 AI API 适配器
|
||||
|
||||
支持智谱 AI 的 GLM 系列模型,使用 OpenAI 兼容的接口格式。
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import OpenAICompatAdapter, RequestData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..service import LLMModel
|
||||
|
||||
|
||||
class ZhipuAdapter(OpenAICompatAdapter):
|
||||
"""智谱AI适配器 - 使用智谱AI专用的OpenAI兼容接口"""
|
||||
|
||||
@property
|
||||
def api_type(self) -> str:
|
||||
return "zhipu"
|
||||
|
||||
@property
|
||||
def supported_api_types(self) -> list[str]:
|
||||
return ["zhipu"]
|
||||
|
||||
def get_chat_endpoint(self) -> str:
|
||||
"""返回智谱AI聊天完成端点"""
|
||||
return "/api/paas/v4/chat/completions"
|
||||
|
||||
def get_embedding_endpoint(self) -> str:
|
||||
"""返回智谱AI嵌入端点"""
|
||||
return "/v4/embeddings"
|
||||
|
||||
def prepare_simple_request(
|
||||
self,
|
||||
model: "LLMModel",
|
||||
api_key: str,
|
||||
prompt: str,
|
||||
history: list[dict[str, str]] | None = None,
|
||||
) -> RequestData:
|
||||
"""准备简单文本生成请求 - 智谱AI优化实现"""
|
||||
url = self.get_api_url(model, self.get_chat_endpoint())
|
||||
headers = self.get_base_headers(api_key)
|
||||
|
||||
messages = []
|
||||
if history:
|
||||
messages.extend(history)
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
body = {
|
||||
"model": model.model_name,
|
||||
"messages": messages,
|
||||
}
|
||||
|
||||
body = self.apply_config_override(model, body)
|
||||
|
||||
return RequestData(url=url, headers=headers, body=body)
|
||||
Reference in New Issue
Block a user