2025-06-21 16:33:21 +08:00
|
|
|
|
"""
|
2026-07-03 08:53:56 +08:00
|
|
|
|
向下兼容门面 (Facade)
|
2025-06-21 16:33:21 +08:00
|
|
|
|
|
2026-07-03 08:53:56 +08:00
|
|
|
|
利用 sys.modules 魔法,将对 zhenxun.services.llm 及其子模块的导入
|
|
|
|
|
|
无缝重定向到 zhenxun.services.ai.llm 下,避免破坏任何第三方插件。
|
|
|
|
|
|
同时在此提供最常用的类导出,作为 LLM 服务的统一入口。
|
2025-06-21 16:33:21 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
2026-07-03 08:53:56 +08:00
|
|
|
|
import sys
|
|
|
|
|
|
from types import ModuleType
|
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
|
|
from zhenxun.services import logger
|
|
|
|
|
|
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
"zhenxun.services.llm 已被重构迁移至 zhenxun.services.ai.llm,"
|
|
|
|
|
|
"为了更好的性能和兼容性,请及时更新您的插件导入路径。"
|
2025-06-21 16:33:21 +08:00
|
|
|
|
)
|
2026-07-03 08:53:56 +08:00
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel, ConfigDict
|
|
|
|
|
|
|
|
|
|
|
|
import zhenxun.services.ai.core
|
|
|
|
|
|
import zhenxun.services.ai.core.exceptions
|
|
|
|
|
|
import zhenxun.services.ai.core.messages
|
|
|
|
|
|
from zhenxun.services.ai.core.messages import ChatResponse as LLMResponse
|
|
|
|
|
|
from zhenxun.services.ai.core.messages import LLMContentPart, ToolCallPart
|
|
|
|
|
|
import zhenxun.services.ai.core.models
|
|
|
|
|
|
from zhenxun.services.ai.core.models import ToolDefinition
|
|
|
|
|
|
import zhenxun.services.ai.core.options
|
|
|
|
|
|
import zhenxun.services.ai.core.protocols
|
|
|
|
|
|
from zhenxun.services.ai.core.protocols.tool import ToolExecutable
|
|
|
|
|
|
import zhenxun.services.ai.llm
|
|
|
|
|
|
import zhenxun.services.ai.llm.system.capabilities
|
|
|
|
|
|
from zhenxun.services.ai.run import RunContext as RealRunContext
|
|
|
|
|
|
import zhenxun.services.ai.tools
|
|
|
|
|
|
from zhenxun.services.ai.tools.models import ToolResult
|
|
|
|
|
|
|
|
|
|
|
|
_legacy_types_module = ModuleType("zhenxun.services.llm.types")
|
|
|
|
|
|
for _mod in (
|
|
|
|
|
|
zhenxun.services.ai.core.options,
|
|
|
|
|
|
zhenxun.services.ai.core.exceptions,
|
|
|
|
|
|
zhenxun.services.ai.core.messages,
|
|
|
|
|
|
zhenxun.services.ai.core.models,
|
|
|
|
|
|
):
|
|
|
|
|
|
for _name in dir(_mod):
|
|
|
|
|
|
if not _name.startswith("_"):
|
|
|
|
|
|
setattr(_legacy_types_module, _name, getattr(_mod, _name))
|
|
|
|
|
|
|
|
|
|
|
|
sys.modules["zhenxun.services.llm.types"] = _legacy_types_module
|
|
|
|
|
|
sys.modules["zhenxun.services.llm.types.models"] = zhenxun.services.ai.core.models
|
|
|
|
|
|
sys.modules["zhenxun.services.llm.types.protocols"] = zhenxun.services.ai.core.protocols
|
|
|
|
|
|
sys.modules["zhenxun.services.llm.types.exceptions"] = (
|
|
|
|
|
|
zhenxun.services.ai.core.exceptions
|
2025-06-21 16:33:21 +08:00
|
|
|
|
)
|
2026-07-03 08:53:56 +08:00
|
|
|
|
sys.modules["zhenxun.services.llm.types.capabilities"] = (
|
|
|
|
|
|
zhenxun.services.ai.llm.system.capabilities
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
prefix_old = "zhenxun.services.llm"
|
|
|
|
|
|
prefix_new = "zhenxun.services.ai.llm"
|
|
|
|
|
|
|
|
|
|
|
|
for module_name, module_obj in list(sys.modules.items()):
|
|
|
|
|
|
if module_name.startswith(prefix_new):
|
|
|
|
|
|
old_module_name = module_name.replace(prefix_new, prefix_old, 1)
|
|
|
|
|
|
if old_module_name not in sys.modules:
|
|
|
|
|
|
sys.modules[old_module_name] = module_obj
|
|
|
|
|
|
|
|
|
|
|
|
sys.modules["zhenxun.services.llm.tools"] = zhenxun.services.ai.tools
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LLMToolFunction(BaseModel):
|
|
|
|
|
|
name: str
|
|
|
|
|
|
arguments: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LLMToolCall(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
function: LLMToolFunction
|
|
|
|
|
|
thought_signature: str | None = None
|
|
|
|
|
|
type: str = "function"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _FakeFunction:
|
|
|
|
|
|
def __init__(self, name, args):
|
|
|
|
|
|
self.name = name
|
|
|
|
|
|
self.arguments = (
|
|
|
|
|
|
args
|
|
|
|
|
|
if isinstance(args, str)
|
|
|
|
|
|
else __import__("json").dumps(args, ensure_ascii=False)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def _legacy_function(self) -> _FakeFunction:
|
|
|
|
|
|
return _FakeFunction(self.tool_name, self.args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setattr(ToolCallPart, "function", _legacy_function) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def _legacy_thought_signature(self) -> Any:
|
|
|
|
|
|
return self.metadata.get("thought_signature") if self.metadata else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@_legacy_thought_signature.setter
|
|
|
|
|
|
def _legacy_thought_signature(self, value: Any) -> None:
|
|
|
|
|
|
if self.metadata is None:
|
|
|
|
|
|
self.metadata = {}
|
|
|
|
|
|
self.metadata["thought_signature"] = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setattr(ToolCallPart, "thought_signature", _legacy_thought_signature) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
from zhenxun.services.ai.core.messages import LLMMessage
|
|
|
|
|
|
|
|
|
|
|
|
_original_assistant_tool_calls = LLMMessage.assistant_tool_calls
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def _shim_assistant_tool_calls(
|
|
|
|
|
|
cls, tool_calls: Any, content: Any = "", scope: Any = None
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
converted = []
|
|
|
|
|
|
for tc in tool_calls:
|
|
|
|
|
|
if hasattr(tc, "function") and not isinstance(tc, ToolCallPart):
|
|
|
|
|
|
converted.append(
|
|
|
|
|
|
ToolCallPart(
|
|
|
|
|
|
id=tc.id, tool_name=tc.function.name, args=tc.function.arguments
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
converted.append(tc)
|
|
|
|
|
|
return _original_assistant_tool_calls(converted, content, scope) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setattr(LLMMessage, "assistant_tool_calls", _shim_assistant_tool_calls) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
if not hasattr(LLMMessage, "name"):
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def _legacy_name(self) -> Any:
|
|
|
|
|
|
return getattr(self, "source_name", None)
|
|
|
|
|
|
|
|
|
|
|
|
@_legacy_name.setter
|
|
|
|
|
|
def _legacy_name(self, value: Any) -> None:
|
|
|
|
|
|
self.source_name = value
|
|
|
|
|
|
|
|
|
|
|
|
setattr(LLMMessage, "name", _legacy_name) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
if not hasattr(LLMMessage, "tool_call_id"):
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def _legacy_tool_call_id(self) -> Any:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@_legacy_tool_call_id.setter
|
|
|
|
|
|
def _legacy_tool_call_id(self, value: Any) -> None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
setattr(LLMMessage, "tool_call_id", _legacy_tool_call_id) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
from zhenxun.services.ai.core.options import GenerationConfig, ReasoningEffort
|
|
|
|
|
|
|
|
|
|
|
|
mod_config_gen = ModuleType("zhenxun.services.llm.config.generation")
|
|
|
|
|
|
sys.modules["zhenxun.services.llm.config"] = ModuleType("zhenxun.services.llm.config")
|
|
|
|
|
|
sys.modules["zhenxun.services.llm.config.generation"] = mod_config_gen
|
|
|
|
|
|
|
2025-06-21 16:33:21 +08:00
|
|
|
|
|
2026-07-03 08:53:56 +08:00
|
|
|
|
class ReasoningConfig(BaseModel):
|
|
|
|
|
|
model_config = ConfigDict(extra="allow") # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ToolConfig(BaseModel):
|
|
|
|
|
|
model_config = ConfigDict(extra="allow") # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setattr(mod_config_gen, "LLMGenerationConfig", GenerationConfig) # type: ignore
|
|
|
|
|
|
setattr(mod_config_gen, "ReasoningConfig", ReasoningConfig) # type: ignore
|
|
|
|
|
|
setattr(mod_config_gen, "ToolConfig", ToolConfig) # type: ignore
|
|
|
|
|
|
setattr(mod_config_gen, "ReasoningEffort", ReasoningEffort) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
_target_models_mod = sys.modules["zhenxun.services.llm.types.models"]
|
|
|
|
|
|
setattr(_target_models_mod, "ToolResult", ToolResult) # type: ignore
|
|
|
|
|
|
setattr(_target_models_mod, "LLMToolCall", LLMToolCall) # type: ignore
|
|
|
|
|
|
setattr(_target_models_mod, "LLMToolFunction", LLMToolFunction) # type: ignore
|
|
|
|
|
|
setattr(_target_models_mod, "LLMContentPart", LLMContentPart) # type: ignore
|
|
|
|
|
|
setattr(_target_models_mod, "ToolDefinition", ToolDefinition) # type: ignore
|
|
|
|
|
|
setattr(_target_models_mod, "LLMResponse", LLMResponse) # type: ignore
|
|
|
|
|
|
setattr(_target_models_mod, "LLMMessage", LLMMessage) # type: ignore
|
|
|
|
|
|
setattr(
|
|
|
|
|
|
sys.modules["zhenxun.services.llm.types.protocols"],
|
|
|
|
|
|
"ToolExecutable",
|
|
|
|
|
|
ToolExecutable,
|
|
|
|
|
|
) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LegacyRunContextShim:
|
|
|
|
|
|
"""拦截旧版 RunContext(extra={...}) 的调用,转化为新版支持的 state"""
|
|
|
|
|
|
|
|
|
|
|
|
def __new__(cls, session_id=None, extra=None, scope=None, **kwargs):
|
|
|
|
|
|
state = kwargs.get("state", {})
|
|
|
|
|
|
if extra:
|
|
|
|
|
|
state.update(extra)
|
|
|
|
|
|
if scope:
|
|
|
|
|
|
state.update(scope)
|
|
|
|
|
|
ctx = RealRunContext(session_id=session_id, state=state)
|
|
|
|
|
|
ctx.extra = ctx.state # type: ignore
|
|
|
|
|
|
ctx.scope = ctx.state # type: ignore
|
|
|
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setattr(sys.modules["zhenxun.services.llm.tools"], "RunContext", LegacyRunContextShim) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ToolInvoker:
|
|
|
|
|
|
"""向下兼容垫片:代替被重构移除的旧版 ToolInvoker"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, callbacks=None):
|
|
|
|
|
|
self.callbacks = callbacks or []
|
|
|
|
|
|
|
|
|
|
|
|
async def execute_tool_call(self, tool_call, available_tools, context=None):
|
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(tool_call, "function") and hasattr(tool_call.function, "name"):
|
|
|
|
|
|
tool_name = tool_call.function.name
|
|
|
|
|
|
args_raw = tool_call.function.arguments
|
|
|
|
|
|
else:
|
|
|
|
|
|
tool_name = getattr(tool_call, "tool_name", "unknown")
|
|
|
|
|
|
args_raw = getattr(tool_call, "args", "{}")
|
|
|
|
|
|
|
|
|
|
|
|
arguments = {}
|
|
|
|
|
|
if args_raw:
|
|
|
|
|
|
if isinstance(args_raw, str):
|
|
|
|
|
|
try:
|
|
|
|
|
|
arguments = json.loads(args_raw)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
elif isinstance(args_raw, dict):
|
|
|
|
|
|
arguments = args_raw
|
|
|
|
|
|
|
|
|
|
|
|
executable = available_tools.get(tool_name)
|
|
|
|
|
|
if not executable:
|
|
|
|
|
|
return tool_call, ToolResult(output=f"Error: Tool '{tool_name}' not found.")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = await executable.execute(context=context, **arguments)
|
|
|
|
|
|
if not hasattr(result, "output"):
|
|
|
|
|
|
result = ToolResult(output=result)
|
|
|
|
|
|
return tool_call, result
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return tool_call, ToolResult(output=f"System Execution Error: {e!s}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import zhenxun.services.ai.tools
|
|
|
|
|
|
|
|
|
|
|
|
setattr(zhenxun.services.ai.tools, "ToolInvoker", ToolInvoker) # type: ignore
|
|
|
|
|
|
setattr(sys.modules["zhenxun.services.llm.tools"], "ToolInvoker", ToolInvoker) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CommonOverrides:
|
|
|
|
|
|
"""向下兼容垫片:由于该类已被废弃,此处提供空实现以防止旧插件导入报错。"""
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _fallback(*args, **kwargs):
|
|
|
|
|
|
from zhenxun.services.ai.llm.builder import IntentBuilder
|
|
|
|
|
|
|
|
|
|
|
|
return IntentBuilder().build()
|
|
|
|
|
|
|
|
|
|
|
|
gemini_json = _fallback
|
|
|
|
|
|
gemini_2_5_thinking = _fallback
|
|
|
|
|
|
gemini_3_thinking = _fallback
|
|
|
|
|
|
gemini_structured = _fallback
|
|
|
|
|
|
gemini_safe = _fallback
|
|
|
|
|
|
gemini_code_execution = _fallback
|
|
|
|
|
|
gemini_grounding = _fallback
|
|
|
|
|
|
gemini_nano_banana = _fallback
|
|
|
|
|
|
gemini_high_res = _fallback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from zhenxun.services.ai.core.options import GenerationConfig, OutputFormatConfig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OutputConfig(OutputFormatConfig):
|
|
|
|
|
|
"""向下兼容垫片:旧版 OutputConfig 等价于 OutputFormatConfig。"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AIConfig = GenerationConfig
|
|
|
|
|
|
LLMGenerationConfig = GenerationConfig
|
|
|
|
|
|
from zhenxun.services.ai.llm import * # noqa: F403
|
|
|
|
|
|
from zhenxun.services.ai.llm.manager import (
|
|
|
|
|
|
get_default_model,
|
2025-06-21 16:33:21 +08:00
|
|
|
|
get_model_instance,
|
|
|
|
|
|
list_available_models,
|
|
|
|
|
|
list_embedding_models,
|
|
|
|
|
|
)
|
2026-07-03 08:53:56 +08:00
|
|
|
|
from zhenxun.services.ai.message_builder import MessageBuilder
|
|
|
|
|
|
|
|
|
|
|
|
message_to_unimessage = MessageBuilder.message_to_unimessage
|
|
|
|
|
|
unimsg_to_llm_parts = MessageBuilder.unimsg_to_llm_parts
|
|
|
|
|
|
from zhenxun.services.ai.core.exceptions import LLMException
|
|
|
|
|
|
from zhenxun.services.ai.core.messages import ChatRequest
|
|
|
|
|
|
from zhenxun.services.ai.core.messages import ChatResponse as LLMResponse
|
|
|
|
|
|
from zhenxun.services.ai.llm.api import generate_structured as _new_generate_structured
|
|
|
|
|
|
from zhenxun.services.ai.llm.engine.router import LLMOrchestrator
|
|
|
|
|
|
from zhenxun.services.ai.tools import tool as function_tool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AI:
|
|
|
|
|
|
"""向下兼容垫片:代替被彻底删除的旧版 AI 类"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, session_id=None, **kwargs):
|
|
|
|
|
|
self.session_id = session_id
|
|
|
|
|
|
|
|
|
|
|
|
async def generate_internal(
|
|
|
|
|
|
self,
|
|
|
|
|
|
messages,
|
|
|
|
|
|
model=None,
|
|
|
|
|
|
config=None,
|
|
|
|
|
|
tools=None,
|
|
|
|
|
|
tool_choice=None,
|
|
|
|
|
|
timeout=None,
|
|
|
|
|
|
):
|
|
|
|
|
|
req = ChatRequest(
|
|
|
|
|
|
messages=messages,
|
|
|
|
|
|
config=config,
|
|
|
|
|
|
tools=tools,
|
|
|
|
|
|
tool_choice=tool_choice,
|
|
|
|
|
|
timeout=timeout,
|
|
|
|
|
|
)
|
|
|
|
|
|
if self.session_id:
|
|
|
|
|
|
req.extra["session_id"] = self.session_id
|
|
|
|
|
|
return await LLMOrchestrator.invoke(req, model_name=model, task="chat")
|
|
|
|
|
|
|
|
|
|
|
|
async def generate_structured(
|
|
|
|
|
|
self,
|
|
|
|
|
|
message,
|
|
|
|
|
|
response_model,
|
|
|
|
|
|
model=None,
|
|
|
|
|
|
tools=None,
|
|
|
|
|
|
tool_choice=None,
|
|
|
|
|
|
instruction=None,
|
|
|
|
|
|
timeout=None,
|
|
|
|
|
|
template_vars=None,
|
|
|
|
|
|
config=None,
|
|
|
|
|
|
max_validation_retries=None,
|
|
|
|
|
|
validation_callback=None,
|
|
|
|
|
|
error_prompt_template=None,
|
|
|
|
|
|
auto_thinking=False,
|
|
|
|
|
|
):
|
|
|
|
|
|
return await _new_generate_structured(
|
|
|
|
|
|
message=message,
|
|
|
|
|
|
response_model=response_model,
|
|
|
|
|
|
model=model,
|
|
|
|
|
|
instruction=instruction,
|
|
|
|
|
|
timeout=timeout,
|
|
|
|
|
|
config=config,
|
|
|
|
|
|
max_retries=max_validation_retries,
|
|
|
|
|
|
error_prompt_template=error_prompt_template,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-06-21 16:33:21 +08:00
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
|
|
"AI",
|
|
|
|
|
|
"AIConfig",
|
|
|
|
|
|
"CommonOverrides",
|
2026-07-03 08:53:56 +08:00
|
|
|
|
"GenerationConfig",
|
2025-06-21 16:33:21 +08:00
|
|
|
|
"LLMContentPart",
|
|
|
|
|
|
"LLMException",
|
|
|
|
|
|
"LLMGenerationConfig",
|
|
|
|
|
|
"LLMMessage",
|
|
|
|
|
|
"LLMResponse",
|
2026-07-03 08:53:56 +08:00
|
|
|
|
"LLMToolCall",
|
|
|
|
|
|
"LLMToolFunction",
|
|
|
|
|
|
"OutputConfig",
|
|
|
|
|
|
"ToolDefinition",
|
|
|
|
|
|
"ToolExecutable",
|
2025-12-14 20:27:02 +08:00
|
|
|
|
"ToolInvoker",
|
2026-07-03 08:53:56 +08:00
|
|
|
|
"ToolResult",
|
2025-08-04 23:36:12 +08:00
|
|
|
|
"function_tool",
|
2026-07-03 08:53:56 +08:00
|
|
|
|
"get_default_model",
|
2025-06-21 16:33:21 +08:00
|
|
|
|
"get_model_instance",
|
|
|
|
|
|
"list_available_models",
|
|
|
|
|
|
"list_embedding_models",
|
2025-07-08 11:15:15 +08:00
|
|
|
|
"message_to_unimessage",
|
2025-06-21 16:33:21 +08:00
|
|
|
|
"unimsg_to_llm_parts",
|
|
|
|
|
|
]
|