mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-08 21:30:01 +08:00
✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 (#2146)
* ✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 - 【重构】将原 services/llm 重构并迁移至全新的 services/ai 架构,提供向下兼容垫片 - 【新增】引入 Agent、Team、Workflow 三大智能体与工作流编排范式 - 【新增】引入基于 RAG 的长期向量记忆与中期槽位记忆系统 - 【新增】引入基于 Docker 的安全代码执行沙箱环境 - 【新增】支持 MCP 协议,允许动态管理和调用 MCP 服务 - 【新增】引入输入输出安全合规护栏与自愈反思机制 - 【优化】重构并优化多厂商 API 适配器 (Gemini, OpenAI, DeepSeek, GLM 等) - 【优化】优化日志脱敏与 Token 预估机制 - 【移除】移除旧版 llm default 和 llm reset-key 命令,新增 llm mcp 管理命令 * 🔧 chore(deps): 更新项目依赖与配置 - 添加 mcp、jieba 和 aiodocker 依赖到配置文件及 requirements.txt - 在 pyright 配置中设置 reportMissingImports 为 none - 调整 .gitignore 中 resources 目录的忽略规则 * ♻️ refactor(tools): 重构工具终止机制并清理知识库日志输出 - 统一使用 `context.state["__end_run__"]` 替代 `EndRunResult` 控制任务结束 - 移除文件系统和向量知识库检索工具中 `ToolResult` 的 `.with_log` 调用 - 调整指令处理器(Directive)的返回值为 `tool_res.output` - 修复部分类型检查警告并优化联合类型判断语法 * ♻️ refactor(tools): 重构工具副作用指令与控制流熔断机制 - 引入 `DirectivePayload` 及 `ToolResult` 的子类以结构化表达工具副作用 - 移除通过 `context.state` 传递魔术变量的隐式控制流设计 - 重构 `DirectiveManager` 处理器接口,直接在处理器中修改 `AgentState` 并构建 `AgentRunResult` - 在 `StandardAgentExecutor` 中统一通过 `directive_manager` 调度工具返回的副作用指令 - 补全 `MessageBuilder` 中部分核心方法的文档注释 * 🐛 fix(sandbox): 修复 Docker 沙箱容器状态检测与会话清理逻辑 -【修复】修正 `is_alive` 中直接读取私有属性的问题,改用 `show()` 返回值 -【修复】解决 `execute_code` 中缓存的执行器与当前会话不一致的问题 -【优化】在清理工作区前增加容器存活检测,避免向已死容器发送请求 -【优化】创建容器时增加运行状态校验,若已停止则自动从缓存中移除并重建 -【优化】优化容器销毁和清理逻辑,静默处理容器不存在 (404) 的异常 * 📝 docs(core): 补充核心模块初始化方法的文档注释 * 🚨 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
bdc1374848
commit
80fc5b86a7
@@ -1,9 +1,9 @@
|
||||
from typing import Any
|
||||
import time
|
||||
from typing import Any, Literal
|
||||
|
||||
from zhenxun import ui
|
||||
from zhenxun.services import renderer_service
|
||||
from zhenxun.services.llm.core import KeyStatus
|
||||
from zhenxun.services.llm.types import ModelModality
|
||||
from zhenxun.services.ai.core.models import ModelModality
|
||||
from zhenxun.ui.models import StatusBadgeCell, TextCell
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class Presenters:
|
||||
|
||||
cap_list = []
|
||||
if ModelModality.IMAGE in caps.input_modalities:
|
||||
cap_list.append("视觉")
|
||||
cap_list.append("图片")
|
||||
if ModelModality.VIDEO in caps.input_modalities:
|
||||
cap_list.append("视频")
|
||||
if ModelModality.AUDIO in caps.input_modalities:
|
||||
@@ -93,11 +93,16 @@ class Presenters:
|
||||
md.head("模型详情", 2)
|
||||
|
||||
temp_value = model.temperature or provider.temperature or "未设置"
|
||||
token_value = model.max_tokens or provider.max_tokens or "未设置"
|
||||
input_tokens = caps.max_input_tokens
|
||||
context_window = (
|
||||
f"{int(input_tokens / 1000)}K"
|
||||
if input_tokens >= 1000
|
||||
else str(input_tokens)
|
||||
)
|
||||
|
||||
md.text(f"- **名称**: {model.model_name}")
|
||||
md.text(f"- **默认温度**: {temp_value}")
|
||||
md.text(f"- **最大Token**: {token_value}")
|
||||
md.text(f"- **上下文窗口**: {context_window}")
|
||||
md.text(f"- **核心能力**: {', '.join(cap_list) or '纯文本'}")
|
||||
|
||||
return await renderer_service.render(md.with_style("light"))
|
||||
@@ -112,33 +117,41 @@ class Presenters:
|
||||
data_list = []
|
||||
|
||||
for key_info in sorted_stats:
|
||||
status_enum: KeyStatus = key_info["status_enum"]
|
||||
status_str = key_info.get("status", "HEALTHY")
|
||||
successes = key_info.get("successes", 0)
|
||||
failures = key_info.get("failures", 0)
|
||||
total_calls = successes + failures
|
||||
|
||||
if status_enum == KeyStatus.COOLDOWN:
|
||||
cooldown_seconds = int(key_info["cooldown_seconds_left"])
|
||||
if total_calls == 0 and status_str == "HEALTHY":
|
||||
status_str = "UNUSED"
|
||||
|
||||
if status_str == "COOLDOWN":
|
||||
cooldown_seconds = max(
|
||||
0, int(key_info.get("cooldown_until", 0) - time.time())
|
||||
)
|
||||
formatted_time = _format_seconds(cooldown_seconds)
|
||||
status_cell = StatusBadgeCell(
|
||||
text=f"冷却中({formatted_time})", status_type="info"
|
||||
)
|
||||
else:
|
||||
status_map = {
|
||||
KeyStatus.DISABLED: ("永久禁用", "error"),
|
||||
KeyStatus.ERROR: ("错误", "error"),
|
||||
KeyStatus.WARNING: ("告警", "warning"),
|
||||
KeyStatus.HEALTHY: ("健康", "ok"),
|
||||
KeyStatus.UNUSED: ("未使用", "info"),
|
||||
status_map: dict[
|
||||
str,
|
||||
tuple[str, Literal["ok", "error", "warning", "info", "success"]],
|
||||
] = {
|
||||
"DISABLED": ("永久禁用", "error"),
|
||||
"ERROR": ("错误", "error"),
|
||||
"WARNING": ("告警", "warning"),
|
||||
"HEALTHY": ("健康", "ok"),
|
||||
"UNUSED": ("未使用", "info"),
|
||||
}
|
||||
text, status_type = status_map.get(status_enum, ("未知", "info"))
|
||||
status_cell = StatusBadgeCell(text=text, status_type=status_type) # type: ignore
|
||||
text, status_type = status_map.get(status_str, ("未知", "info"))
|
||||
status_cell = StatusBadgeCell(text=text, status_type=status_type)
|
||||
|
||||
total_calls = key_info["total_calls"]
|
||||
total_calls_text = (
|
||||
f"{key_info['success_count']}/{total_calls}"
|
||||
if total_calls > 0
|
||||
else "0/0"
|
||||
f"{successes}/{total_calls}" if total_calls > 0 else "0/0"
|
||||
)
|
||||
|
||||
success_rate = key_info["success_rate"]
|
||||
success_rate = (successes / total_calls * 100) if total_calls > 0 else 100.0
|
||||
success_rate_text = f"{success_rate:.1f}%" if total_calls > 0 else "N/A"
|
||||
rate_color = None
|
||||
if total_calls > 0:
|
||||
@@ -148,13 +161,18 @@ class Presenters:
|
||||
rate_color = "#E6A23C"
|
||||
success_rate_cell = TextCell(content=success_rate_text, color=rate_color)
|
||||
|
||||
avg_latency = key_info["avg_latency"]
|
||||
avg_latency_text = f"{avg_latency / 1000:.2f}" if avg_latency > 0 else "N/A"
|
||||
avg_latency_text = "N/A"
|
||||
|
||||
last_error = key_info.get("last_error") or "-"
|
||||
if len(last_error) > 25:
|
||||
last_error = last_error[:22] + "..."
|
||||
|
||||
suggested_action = "-"
|
||||
if status_str == "DISABLED":
|
||||
suggested_action = "检查配额或换Key"
|
||||
elif status_str == "COOLDOWN":
|
||||
suggested_action = "等待恢复"
|
||||
|
||||
data_list.append(
|
||||
[
|
||||
TextCell(content=key_info["key_id"]),
|
||||
@@ -163,7 +181,7 @@ class Presenters:
|
||||
success_rate_cell,
|
||||
TextCell(content=avg_latency_text),
|
||||
TextCell(content=last_error),
|
||||
TextCell(content=key_info["suggested_action"]),
|
||||
TextCell(content=suggested_action),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -181,3 +199,38 @@ class Presenters:
|
||||
)
|
||||
table.add_rows(data_list)
|
||||
return await renderer_service.render(table, use_cache=False)
|
||||
|
||||
@staticmethod
|
||||
async def format_mcp_list_as_image(mcp_list: list[dict[str, Any]]) -> bytes:
|
||||
"""将MCP列表格式化为表格图片"""
|
||||
title = "MCP 服务管理列表"
|
||||
if not mcp_list:
|
||||
table = ui.table(title=title, tip="当前未配置任何 MCP 服务。").set_headers(
|
||||
["ID", "MCP名称", "协议", "状态", "目标"]
|
||||
)
|
||||
return await renderer_service.render(table)
|
||||
|
||||
column_name = ["ID", "MCP名称", "协议", "状态", "目标"]
|
||||
rows_data = []
|
||||
for mcp in mcp_list:
|
||||
is_enable = mcp["enabled"]
|
||||
status_type = "success" if is_enable else "info"
|
||||
status_text = "开启" if is_enable else "关闭"
|
||||
rows_data.append(
|
||||
[
|
||||
TextCell(content=str(mcp["id"])),
|
||||
TextCell(content=mcp["name"]),
|
||||
TextCell(content=mcp["transport"]),
|
||||
StatusBadgeCell(text=status_text, status_type=status_type),
|
||||
TextCell(content=mcp["target"]),
|
||||
]
|
||||
)
|
||||
|
||||
table = ui.table(
|
||||
title=title,
|
||||
tip="使用 `llm mcp 开启/关闭 <ID/名称>` 来修改状态,支持批量操作",
|
||||
)
|
||||
table.set_headers(column_name)
|
||||
table.set_column_alignments(["center", "left", "left", "center", "left"])
|
||||
table.add_rows(rows_data)
|
||||
return await renderer_service.render(table, use_cache=False)
|
||||
|
||||
Reference in New Issue
Block a user