zhenxun_bot/zhenxun/builtin_plugins/llm_manager/data_source.py
Rumio 46a0768a45
feat(llm): 新增LLM模型管理插件并增强API密钥管理 (#1972)
🔧 新增功能:
- LLM模型管理插件 (builtin_plugins/llm_manager/)
  • llm list - 查看可用模型列表 (图片格式)
  • llm info - 查看模型详细信息 (Markdown图片)
  • llm default - 管理全局默认模型
  • llm test - 测试模型连通性
  • llm keys - 查看API Key状态 (表格图片,含健康度/成功率/延迟)
  • llm reset-key - 重置API Key失败状态

🏗️ 架构重构:
- 会话管理: AI/AIConfig 类迁移至独立的 session.py
- 类型定义: TaskType 枚举移至 types/enums.py
- API增强:
  • chat() 函数返回完整 LLMResponse,支持工具调用
  • 新增 generate() 函数用于一次性响应生成
  • 统一API调用核心方法 _perform_api_call,返回使用的API密钥

🚀 密钥管理增强:
- 详细状态跟踪: 健康度、成功率、平均延迟、错误信息、建议操作
- 状态持久化: 启动时加载,关闭时自动保存密钥状态
- 智能冷却策略: 根据错误类型设置不同冷却时间
- 延迟监控: with_smart_retry 记录API调用延迟并更新统计

Co-authored-by: webjoin111 <455457521@qq.com>
Co-authored-by: HibiKier <45528451+HibiKier@users.noreply.github.com>
2025-07-14 22:39:17 +08:00

121 lines
4.2 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.

import time
from typing import Any
from zhenxun.services.llm import (
LLMException,
get_global_default_model_name,
get_model_instance,
list_available_models,
set_global_default_model_name,
)
from zhenxun.services.llm.core import KeyStatus
from zhenxun.services.llm.manager import (
reset_key_status,
)
class DataSource:
"""LLM管理插件的数据源和业务逻辑"""
@staticmethod
async def get_model_list(show_all: bool = False) -> list[dict[str, Any]]:
"""获取模型列表"""
models = list_available_models()
if show_all:
return models
return [m for m in models if m.get("is_available", True)]
@staticmethod
async def get_model_details(model_name_str: str) -> dict[str, Any] | None:
"""获取指定模型的详细信息"""
try:
model = await get_model_instance(model_name_str)
return {
"provider_config": model.provider_config,
"model_detail": model.model_detail,
"capabilities": model.capabilities,
}
except LLMException:
return None
@staticmethod
async def get_default_model() -> str | None:
"""获取全局默认模型"""
return get_global_default_model_name()
@staticmethod
async def set_default_model(model_name_str: str) -> tuple[bool, str]:
"""设置全局默认模型"""
success = set_global_default_model_name(model_name_str)
if success:
return True, f"✅ 成功将默认模型设置为: {model_name_str}"
else:
return False, f"❌ 设置失败,模型 '{model_name_str}' 不存在或无效。"
@staticmethod
async def test_model_connectivity(model_name_str: str) -> tuple[bool, str]:
"""测试模型连通性"""
start_time = time.monotonic()
try:
async with await get_model_instance(model_name_str) as model:
await model.generate_text("你好")
end_time = time.monotonic()
latency = (end_time - start_time) * 1000
return (
True,
f"✅ 模型 '{model_name_str}' 连接成功!\n响应延迟: {latency:.2f} ms",
)
except LLMException as e:
return (
False,
f"❌ 模型 '{model_name_str}' 连接测试失败:\n"
f"{e.user_friendly_message}\n错误码: {e.code.name}",
)
except Exception as e:
return False, f"❌ 测试时发生未知错误: {e!s}"
@staticmethod
async def get_key_status(provider_name: str) -> list[dict[str, Any]] | None:
"""获取并排序指定提供商的API Key状态"""
from zhenxun.services.llm.manager import get_key_usage_stats
all_stats = await get_key_usage_stats()
provider_stats = all_stats.get(provider_name)
if not provider_stats or not provider_stats.get("key_stats"):
return None
key_stats_dict = provider_stats["key_stats"]
stats_list = [
{"key_id": key_id, **stats} for key_id, stats in key_stats_dict.items()
]
def sort_key(item: dict[str, Any]):
status_priority = item.get("status_enum", KeyStatus.UNUSED).value
return (
status_priority,
100 - item.get("success_rate", 100.0),
-item.get("total_calls", 0),
)
sorted_stats_list = sorted(stats_list, key=sort_key)
return sorted_stats_list
@staticmethod
async def reset_key(provider_name: str, api_key: str | None) -> tuple[bool, str]:
"""重置API Key状态"""
success = await reset_key_status(provider_name, api_key)
if success:
if api_key:
if len(api_key) > 8:
target = f"API Key '{api_key[:4]}...{api_key[-4:]}'"
else:
target = f"API Key '{api_key}'"
else:
target = "所有API Keys"
return True, f"✅ 成功重置提供商 '{provider_name}'{target} 的状态。"
else:
return False, "❌ 重置失败请检查提供商名称或API Key是否正确。"