Files
Bubbles/commands/ai_router.py

212 lines
7.8 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 re
import json
import logging
from typing import Dict, Callable, Optional, Any, Tuple
from dataclasses import dataclass, field
from .context import MessageContext
logger = logging.getLogger(__name__)
@dataclass
class AIFunction:
"""AI可调用的功能定义"""
name: str # 功能唯一标识名
handler: Callable # 处理函数
description: str # 功能描述给AI看的
examples: list[str] = field(default_factory=list) # 示例用法
params_description: str = "" # 参数说明
class AIRouter:
"""AI智能路由器"""
def __init__(self):
self.functions: Dict[str, AIFunction] = {}
self.logger = logger
def register(self, name: str, description: str, examples: list[str] = None, params_description: str = ""):
"""
装饰器注册一个功能到AI路由器
@ai_router.register(
name="weather_query",
description="查询指定城市的天气预报",
examples=["北京天气怎么样", "查一下上海的天气", "明天深圳会下雨吗"],
params_description="城市名称"
)
def handle_weather(ctx: MessageContext, params: str) -> bool:
# 实现天气查询逻辑
pass
"""
def decorator(func: Callable) -> Callable:
ai_func = AIFunction(
name=name,
handler=func,
description=description,
examples=examples or [],
params_description=params_description
)
self.functions[name] = ai_func
self.logger.info(f"AI路由器注册功能: {name} - {description}")
return func
return decorator
def _build_ai_prompt(self) -> str:
"""构建给AI的系统提示词包含所有可用功能的信息"""
prompt = """你是一个智能路由助手。根据用户的输入判断用户的意图并返回JSON格式的响应。
可用的功能列表:
"""
for name, func in self.functions.items():
prompt += f"\n- {name}: {func.description}"
if func.params_description:
prompt += f"\n 参数: {func.params_description}"
if func.examples:
prompt += f"\n 示例: {', '.join(func.examples[:3])}"
prompt += "\n"
prompt += """
分析用户输入严格按照以下格式返回JSON
如果用户需要使用上述功能之一,返回:
{
"action_type": "function",
"function_name": "上述功能列表中的功能名",
"params": "从用户输入中提取的参数"
}
如果用户只是聊天或者不匹配任何功能,返回:
{
"action_type": "chat"
}
示例:
- 用户输入"北京天气怎么样" -> {"action_type": "function", "function_name": "weather_query", "params": "北京"}
- 用户输入"看看新闻" -> {"action_type": "function", "function_name": "news_query", "params": ""}
- 用户输入"你好" -> {"action_type": "chat"}
- 用户输入"查一下Python教程" -> {"action_type": "function", "function_name": "perplexity_search", "params": "Python教程"}
重要:
1. action_type 只能是 "function""chat"
2. 只返回JSON无需其他解释
3. function_name 必须完全匹配上述功能列表中的名称
"""
return prompt
def route(self, ctx: MessageContext) -> Tuple[bool, Optional[Dict[str, Any]]]:
"""
AI路由决策
返回: (是否处理成功, AI决策结果)
"""
print(f"[AI路由器] route方法被调用")
if not ctx.text:
print("[AI路由器] ctx.text为空返回False")
return False, None
# 获取AI模型
chat_model = getattr(ctx, 'chat', None)
if not chat_model:
chat_model = getattr(ctx.robot, 'chat', None) if ctx.robot else None
if not chat_model:
print("[AI路由器] 无可用的AI模型")
self.logger.error("AI路由器无可用的AI模型")
return False, None
print(f"[AI路由器] 找到AI模型: {type(chat_model)}")
try:
# 构建系统提示词
system_prompt = self._build_ai_prompt()
print(f"[AI路由器] 已构建系统提示词,长度: {len(system_prompt)}")
# 让AI分析用户意图
user_input = f"用户输入:{ctx.text}"
print(f"[AI路由器] 准备调用AI分析意图: {user_input}")
ai_response = chat_model.get_answer(
user_input,
wxid=ctx.get_receiver(),
system_prompt_override=system_prompt
)
print(f"[AI路由器] AI响应: {ai_response}")
# 解析AI返回的JSON
json_match = re.search(r'\{.*\}', ai_response, re.DOTALL)
if not json_match:
self.logger.warning(f"AI路由器无法从AI响应中提取JSON - {ai_response}")
return False, None
decision = json.loads(json_match.group(0))
# 验证决策格式
action_type = decision.get("action_type")
if action_type not in ["chat", "function"]:
self.logger.warning(f"AI路由器未知的action_type - {action_type}")
return False, None
# 如果是功能调用,验证功能名
if action_type == "function":
function_name = decision.get("function_name")
if function_name not in self.functions:
self.logger.warning(f"AI路由器未知的功能名 - {function_name}")
return False, None
self.logger.info(f"AI路由决策: {decision}")
return True, decision
except json.JSONDecodeError as e:
self.logger.error(f"AI路由器解析JSON失败 - {e}")
return False, None
except Exception as e:
self.logger.error(f"AI路由器处理异常 - {e}")
return False, None
def dispatch(self, ctx: MessageContext) -> bool:
"""
执行AI路由分发
返回: 是否成功处理
"""
print(f"[AI路由器] dispatch被调用消息内容: {ctx.text}")
# 获取AI路由决策
success, decision = self.route(ctx)
print(f"[AI路由器] route返回 - success: {success}, decision: {decision}")
if not success or not decision:
print("[AI路由器] route失败或无决策返回False")
return False
action_type = decision.get("action_type")
# 如果是聊天返回False让后续处理器处理
if action_type == "chat":
self.logger.info("AI路由器识别为聊天意图交给聊天处理器")
return False
# 如果是功能调用
if action_type == "function":
function_name = decision.get("function_name")
params = decision.get("params", "")
func = self.functions.get(function_name)
if not func:
self.logger.error(f"AI路由器功能 {function_name} 未找到")
return False
try:
self.logger.info(f"AI路由器调用功能 {function_name},参数: {params}")
result = func.handler(ctx, params)
return result
except Exception as e:
self.logger.error(f"AI路由器执行功能 {function_name} 出错 - {e}")
return False
return False
# 创建全局AI路由器实例
ai_router = AIRouter()