Files
qihuaiflow/backend/python/app/api/chat.py
T
qyyfw 78ff261b5a 初始提交: Qihu-AIFlow v2.0.1 完整项目
- 前端 React+TypeScript (源码+构建产物)
- 后端 Python FastAPI
- 数据库迁移 001-015
- Docker部署配置
- 微信ClawBot API频道绑定
- 项目文档 README.md
2026-07-02 14:40:46 +08:00

782 lines
30 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.
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import and_, desc, asc
from pydantic import BaseModel, Field
from typing import Optional
import uuid
from datetime import datetime
import re
import random
import json
import httpx
import os
import logging
from ..database import get_db, SessionLocal
from ..models import (
User, Conversation, Message, AIModel, ModelProvider, TokenUsageLog,
KnowledgeBase, KnowledgeDocument, EvolutionSkill,
EvolutionTrajectory,
gen_uuid
)
from ..auth import get_current_user
from ..middleware.log_service import LogService
router = APIRouter()
logger = logging.getLogger(__name__)
class CreateConversationRequest(BaseModel):
title: str = Field(default="新对话")
model_id: Optional[str] = None
class UpdateConversationRequest(BaseModel):
title: Optional[str] = None
status: Optional[str] = None # active, archived
pinned: Optional[bool] = None
class SendMessageRequest(BaseModel):
content: str
model_id: Optional[str] = None
skill_ids: Optional[list] = None
agent_ids: Optional[list] = None
knowledge_base_ids: Optional[list] = None
use_tools: Optional[bool] = False
@router.get("/conversations")
def list_conversations(
status: Optional[str] = Query(None),
search: Optional[str] = Query(None),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
query = db.query(Conversation).filter(Conversation.user_id == current_user.id)
if status:
query = query.filter(Conversation.status == status)
else:
# 默认不返回已归档的
query = query.filter(Conversation.status != "archived")
if search:
query = query.filter(Conversation.title.ilike(f"%{search}%"))
# pinned优先,然后按更新时间倒序
query = query.order_by(desc(Conversation.pinned), desc(Conversation.updated_at))
conversations = query.all()
result = []
for conv in conversations:
result.append({
"id": conv.id,
"title": conv.title,
"model_id": conv.model_id,
"status": conv.status,
"pinned": conv.pinned,
"knowledge_graph_count": conv.knowledge_graph_count or 0,
"created_at": str(conv.created_at) if conv.created_at else None,
"updated_at": str(conv.updated_at) if conv.updated_at else None,
})
return {"code": 0, "data": result, "message": "ok"}
@router.post("/conversations")
def create_conversation(
req: CreateConversationRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
conv = Conversation(
id=gen_uuid(),
user_id=current_user.id,
title=req.title,
model_id=req.model_id,
status="active",
pinned=False,
knowledge_graph_count=0,
created_at=datetime.now(),
updated_at=datetime.now(),
)
db.add(conv)
db.commit()
db.refresh(conv)
return {
"code": 0,
"data": {
"id": conv.id,
"title": conv.title,
"model_id": conv.model_id,
"status": conv.status,
"pinned": conv.pinned,
"knowledge_graph_count": conv.knowledge_graph_count,
"created_at": str(conv.created_at) if conv.created_at else None,
"updated_at": str(conv.updated_at) if conv.updated_at else None,
},
"message": "创建成功",
}
@router.put("/conversations/{conv_id}")
def update_conversation(
conv_id: str,
req: UpdateConversationRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
conv = db.query(Conversation).filter(
and_(Conversation.id == conv_id, Conversation.user_id == current_user.id)
).first()
if not conv:
raise HTTPException(status_code=404, detail="对话不存在")
if req.title is not None:
conv.title = req.title
if req.status is not None:
conv.status = req.status
if req.pinned is not None:
conv.pinned = req.pinned
conv.updated_at = datetime.now()
db.commit()
db.refresh(conv)
return {"code": 0, "data": {
"id": conv.id, "title": conv.title, "status": conv.status, "pinned": conv.pinned,
"updated_at": str(conv.updated_at),
}, "message": "更新成功"}
@router.delete("/conversations/{conv_id}")
def delete_conversation(
conv_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
conv = db.query(Conversation).filter(
and_(Conversation.id == conv_id, Conversation.user_id == current_user.id)
).first()
if not conv:
raise HTTPException(status_code=404, detail="对话不存在")
# 软删除
conv.status = "archived"
conv.updated_at = datetime.now()
db.commit()
return {"code": 0, "message": "删除成功"}
@router.get("/conversations/{conv_id}/messages")
def get_messages(
conv_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
conv = db.query(Conversation).filter(
and_(Conversation.id == conv_id, Conversation.user_id == current_user.id)
).first()
if not conv:
raise HTTPException(status_code=404, detail="对话不存在")
messages = (
db.query(Message)
.filter(Message.conversation_id == conv_id)
.order_by(asc(Message.created_at))
.all()
)
result = []
for msg in messages:
result.append({
"id": msg.id,
"conversation_id": msg.conversation_id,
"role": msg.role,
"content": msg.content,
"token_used": msg.token_used or 0,
"model_id": msg.model_id,
"used_skills": msg.used_skills or [],
"created_at": str(msg.created_at) if msg.created_at else None,
})
return {"code": 0, "data": result, "message": "ok"}
@router.post("/conversations/{conv_id}/messages")
async def send_message(
conv_id: str,
req: SendMessageRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
conv = db.query(Conversation).filter(
and_(Conversation.id == conv_id, Conversation.user_id == current_user.id)
).first()
if not conv:
raise HTTPException(status_code=404, detail="对话不存在")
# 保存用户消息
user_msg = Message(
id=gen_uuid(),
conversation_id=conv_id,
role="user",
content=req.content,
token_used=len(req.content) // 2, # 粗略估算
model_id=req.model_id or conv.model_id,
created_at=datetime.now(),
)
db.add(user_msg)
# 调用 AI 模型 API
user_id = current_user.id
model_id = req.model_id or (conv.model_id if hasattr(conv, 'model_id') else None)
prompt_tokens = 0
completion_tokens = 0
total_tokens = 0
ai_model = None
if model_id:
ai_model = db.query(AIModel).filter(AIModel.id == model_id).first()
if ai_model and ai_model.endpoint:
provider = db.query(ModelProvider).filter(ModelProvider.id == ai_model.provider_id).first()
api_key = ai_model.api_key or (provider.api_key if provider else None)
api_url = ai_model.endpoint
if not api_url and provider and provider.api_base_url:
api_url = f"{provider.api_base_url.rstrip('/')}/v1/chat/completions"
if api_key and api_url:
try:
# 构建消息列表:先system prompt(含skills/agents上下文),再历史消息,再用户消息
messages = []
system_parts = ["你是AIFlow平台的AI助手。"]
# 加载skills信息用于tool-call
skills_list = []
function_definitions = []
if req.skill_ids and len(req.skill_ids) > 0:
skills_list = db.query(EvolutionSkill).filter(EvolutionSkill.id.in_(req.skill_ids)).all()
if skills_list:
skill_descs = ", ".join([f"{s.skill_name}({s.category or 'general'})" for s in skills_list])
system_parts.append(f"可用技能: {skill_descs}")
system_parts.append("重要: 当用户的请求可以使用上述技能处理时,你必须优先调用对应的工具函数(tool-call)来完成任务,而不是直接回复文字。")
# 构建function definitions用于tool-call (OpenAI function calling协议)
if req.use_tools:
for s in skills_list:
func_name = s.skill_name.replace(" ", "_").replace("-", "_").lower()
_desc = s.description or f"{s.skill_name} - {s.category or 'general'}"
function_definitions.append({
"name": func_name,
"description": f"调用技能「{s.skill_name}」处理用户请求。使用场景: {_desc}。当用户请求涉及{s.category or '通用'}类任务时优先调用此工具。",
"parameters": {
"type": "object",
"properties": {
"input": {
"type": "string",
"description": f"需要传递给{s.skill_name}的完整用户输入内容",
}
},
"required": ["input"],
},
})
if req.agent_ids and len(req.agent_ids) > 0:
system_parts.append(f"已选择Agent辅助处理任务")
if req.knowledge_base_ids and len(req.knowledge_base_ids) > 0:
kbs = db.query(KnowledgeBase).filter(KnowledgeBase.id.in_(req.knowledge_base_ids)).all()
if kbs:
kb_names = ", ".join([k.name for k in kbs])
system_parts.append(f"参考知识库: {kb_names}")
messages.append({"role": "system", "content": "; ".join(system_parts)})
# 加载最近的对话历史(最多10条)
history = db.query(Message).filter(
Message.conversation_id == conv_id
).order_by(asc(Message.created_at)).limit(10).all()
for h in history:
messages.append({"role": h.role, "content": h.content})
messages.append({"role": "user", "content": req.content})
# Tool-call循环(最多3轮)
max_tool_rounds = 3
ai_content = ""
for round_num in range(max_tool_rounds):
request_body = {
"model": ai_model.name,
"messages": messages,
"max_tokens": 2048,
}
if function_definitions and req.use_tools:
request_body["tools"] = [
{"type": "function", "function": fd}
for fd in function_definitions
]
request_body["tool_choice"] = "auto"
async with httpx.AsyncClient(timeout=ai_model.timeout or 60) as client:
resp = await client.post(
api_url,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
},
json=request_body
)
if resp.status_code != 200:
if round_num == 0:
ai_content = f"AI模型调用失败 (HTTP {resp.status_code}),请检查模型配置。"
break
data = resp.json()
choice = data.get("choices", [{}])[0]
message = choice.get("message", {})
# 记录token使用量
usage = data.get("usage", {})
prompt_tokens += usage.get("prompt_tokens", 0)
completion_tokens += usage.get("completion_tokens", 0)
total_tokens += usage.get("total_tokens", 0)
# 检查是否有tool_calls
tool_calls = message.get("tool_calls", [])
if tool_calls and req.use_tools:
# 处理tool call:将assistant消息(含tool_calls)加入上下文
messages.append({
"role": "assistant",
"content": message.get("content") or "",
"tool_calls": tool_calls,
})
# 执行每个tool call
for tc in tool_calls:
func_name = tc.get("function", {}).get("name", "")
func_args_str = tc.get("function", {}).get("arguments", "{}")
try:
func_args = json.loads(func_args_str)
except Exception:
func_args = {"input": func_args_str}
# 查找匹配的skill并生成tool结果
tool_result = _execute_skill_tool(db, func_name, func_args, skills_list)
messages.append({
"role": "tool",
"tool_call_id": tc.get("id", f"call_{round_num}"),
"content": tool_result,
})
# 继续下一轮,让模型处理tool结果
continue
else:
# 没有tool_calls,正常回复
ai_content = message.get("content", "")
if not ai_content and round_num > 0:
ai_content = "AI模型已处理工具调用结果,但返回了空响应。"
break
if not ai_content:
ai_content = "AI模型返回了空响应,请稍后重试。"
except Exception as e:
import logging
logging.getLogger(__name__).warning(f"AI API调用异常: {e}")
ai_content = "AI服务暂时不可用,请稍后重试或检查模型API配置。"
else:
ai_content = "该模型缺少API密钥或端点配置,请在后台管理-大模型库中配置API Key和Endpoint。"
else:
ai_content = "请选择一个已配置的AI模型进行对话。"
else:
ai_content = "请先在聊天设置中选择一个AI模型。"
ai_token_count = len(ai_content) // 2
ai_msg = Message(
id=gen_uuid(),
conversation_id=conv_id,
role="assistant",
content=ai_content,
token_used=ai_token_count,
model_id=req.model_id or conv.model_id,
created_at=datetime.now(),
)
db.add(ai_msg)
# 基于请求中skill_ids关联用户真实技能作为used_skills
used_skills = []
if req.skill_ids and len(req.skill_ids) > 0:
selected_skills = db.query(EvolutionSkill).filter(
EvolutionSkill.id.in_(req.skill_ids),
EvolutionSkill.user_id == current_user.id
).all()
if selected_skills:
used_skills = [s.skill_name for s in selected_skills]
ai_msg.used_skills = used_skills
# 更新每个被使用技能的use_count和last_used_at
now = datetime.now()
for s in selected_skills:
s.use_count = (s.use_count or 0) + 1
s.last_used_at = now
# 更新对话统计
conv.knowledge_graph_count = (conv.knowledge_graph_count or 0) + 1
conv.updated_at = datetime.now()
# 记录Token使用
token_usage = TokenUsageLog(
id=gen_uuid(),
user_id=current_user.id,
model_id=req.model_id or conv.model_id,
model_name=ai_model.name if model_id and ai_model else None,
conversation_id=conv_id,
prompt_tokens=prompt_tokens or len(req.content) // 2,
completion_tokens=completion_tokens or ai_token_count,
total_tokens=total_tokens or (len(req.content) // 2 + ai_token_count),
created_at=datetime.now(),
)
db.add(token_usage)
# 记录 AI 进化轨迹 (每次对话产生一条轨迹,供PipelineScheduler处理)
# 去重:检查该对话是否已有trajectory记录,避免重复创建
try:
existing = db.query(EvolutionTrajectory).filter(
EvolutionTrajectory.conversation_id == conv_id
).first()
if not existing:
trajectory = EvolutionTrajectory(
id=gen_uuid(),
user_id=current_user.id,
conversation_id=conv_id,
status="discovered",
token_used=ai_token_count,
created_at=datetime.now(),
)
db.add(trajectory)
db.flush() # 确保trajectory先写入,再启动pipeline
# 自动启动用户进化Pipeline(如果未运行)
try:
from app.services.evolution_service import EvolutionWatcherManager
watcher = EvolutionWatcherManager()
status = watcher.get_user_status(current_user.id, lambda: SessionLocal())
if not status.get("scheduler_running"):
watcher.start_user_evolution(current_user.id, lambda: SessionLocal())
logger.info(f"[Evolution] 自动启动用户 {current_user.id} 的进化Pipeline")
except Exception as evolution_err:
logger.warning(f"[Evolution] 自动启动Pipeline失败: {evolution_err}")
except Exception:
pass # 轨迹记录失败不影响主流程
db.commit()
db.refresh(user_msg)
db.refresh(ai_msg)
return {
"code": 0,
"data": {
"user_message": {
"id": user_msg.id, "role": user_msg.role, "content": user_msg.content,
"token_used": user_msg.token_used, "created_at": str(user_msg.created_at),
},
"assistant_message": {
"id": ai_msg.id, "role": ai_msg.role, "content": ai_msg.content,
"token_used": ai_msg.token_used, "created_at": str(ai_msg.created_at),
"used_skills": used_skills,
},
},
"message": "发送成功",
}
@router.post("/conversations/{conv_id}/clear")
def clear_conversation(
conv_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
conv = db.query(Conversation).filter(
and_(Conversation.id == conv_id, Conversation.user_id == current_user.id)
).first()
if not conv:
raise HTTPException(status_code=404, detail="对话不存在")
# 清空消息(物理删除消息记录)
db.query(Message).filter(
Message.conversation_id == conv_id
).delete()
conv.knowledge_graph_count = 0
conv.updated_at = datetime.now()
db.commit()
return {"code": 0, "message": "对话上下文已清空"}
@router.post("/conversations/{conv_id}/to-knowledge-graph")
def convert_to_knowledge_graph(
conv_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""将对话内容转换为知识图谱:提取消息内容、创建知识库、构建知识图谱"""
conv = db.query(Conversation).filter(
and_(Conversation.id == conv_id, Conversation.user_id == current_user.id)
).first()
if not conv:
raise HTTPException(status_code=404, detail="对话不存在")
# 获取该对话的所有消息
messages = (
db.query(Message)
.filter(Message.conversation_id == conv_id)
.order_by(asc(Message.created_at))
.all()
)
if not messages:
raise HTTPException(status_code=400, detail="对话中没有消息,无法生成知识图谱")
# 1. 自动创建一个知识库(名为"对话图谱-{对话标题}")
kb_name = f"对话图谱-{conv.title}"
kb = KnowledgeBase(
id=gen_uuid(),
user_id=current_user.id,
name=kb_name,
description=f"从对话「{conv.title}」自动生成的知识图谱",
directory_path="/",
document_count=0,
created_at=datetime.now(),
updated_at=datetime.now(),
)
db.add(kb)
db.flush()
# 2. 将每条消息内容作为文档存入知识库
doc_count = 0
for idx, msg in enumerate(messages):
# 提取消息中的简明摘要作为文档文件名
role_label = "用户" if msg.role == "user" else "AI"
preview = re.sub(r'[\\n\\r\\t]+', ' ', msg.content[:40]).strip()
filename = f"msg_{idx+1:03d}_{role_label}_{preview}.txt"
doc = KnowledgeDocument(
id=gen_uuid(),
knowledge_base_id=kb.id,
filename=filename,
file_type="txt",
file_size=len(msg.content.encode('utf-8')),
status="completed",
chunk_count=max(1, len(msg.content) // 500 + 1),
created_at=datetime.now(),
updated_at=datetime.now(),
)
db.add(doc)
doc_count += 1
kb.document_count = doc_count
db.flush()
# 3. 调用知识图谱构建逻辑(类似 knowledge.py 中的 build-graph)
documents = db.query(KnowledgeDocument).filter(
and_(
KnowledgeDocument.knowledge_base_id == kb.id,
KnowledgeDocument.status == "completed",
)
).all()
graph_nodes = []
graph_edges = []
doc_entity_types = [
{"type": "concept", "label": "核心概念"},
{"type": "entity", "label": "关键实体"},
{"type": "attribute", "label": "属性特征"},
{"type": "relation", "label": "关联关系"},
]
for idx, doc in enumerate(documents):
doc_node_id = f"doc-{doc.id[:8]}"
graph_nodes.append({
"id": doc_node_id,
"label": doc.filename,
"type": "document",
"size": 18,
"group": "document",
})
filename_base = doc.filename.rsplit(".", 1)[0]
words = filename_base.replace("_", " ").replace("-", " ").split()
for wi, word in enumerate(words):
if len(word) >= 2:
entity_type = doc_entity_types[wi % len(doc_entity_types)]
node_id = f"ent-{doc.id[:8]}-{wi}"
graph_nodes.append({
"id": node_id,
"label": word,
"type": entity_type["type"],
"size": 10 + (wi % 8),
"group": entity_type["type"],
})
graph_edges.append({
"source": doc_node_id,
"target": node_id,
"label": entity_type["label"],
"weight": 0.5 + (0.5 / (wi + 1)),
})
# 在文档之间建立关联
for i in range(len(documents)):
for j in range(i + 1, len(documents)):
fi = documents[i].filename.lower().rsplit(".", 1)[0]
fj = documents[j].filename.lower().rsplit(".", 1)[0]
words_i = set(fi.replace("_", " ").replace("-", " ").split())
words_j = set(fj.replace("_", " ").replace("-", " ").split())
common = words_i & words_j
if common and len(common) > 0:
graph_edges.append({
"source": f"doc-{documents[i].id[:8]}",
"target": f"doc-{documents[j].id[:8]}",
"label": f"共有关键词: {', '.join(list(common)[:3])}",
"weight": min(0.9, 0.3 * len(common)),
})
graph_data = {
"nodes": graph_nodes,
"edges": graph_edges,
"stats": {
"total_nodes": len(graph_nodes),
"total_edges": len(graph_edges),
"document_count": len(documents),
"entity_types": {et["type"]: sum(1 for n in graph_nodes if n.get("group") == et["type"])
for et in doc_entity_types},
},
}
# 更新知识库的知识图谱计数
kb.knowledge_graph_count = len(graph_nodes)
kb.updated_at = datetime.now()
# 更新对话的知识图谱计数
conv.knowledge_graph_count = (conv.knowledge_graph_count or 0) + 1
conv.updated_at = datetime.now()
db.commit()
LogService.log_operation(
user_id=current_user.id, username=current_user.username, action="convert_to_graph",
target="conversation", target_id=conv_id,
detail=f"将对话 {conv.title} 转换为知识图谱 {kb.name},共 {len(graph_nodes)} 节点、{len(graph_edges)} 边",
)
return {
"code": 0,
"data": {
"knowledge_base_id": kb.id,
"knowledge_base_name": kb.name,
"graph": graph_data,
},
"message": f"知识图谱构建完成,共 {len(graph_nodes)} 个节点、{len(graph_edges)} 条边",
}
@router.get("/models")
def get_models(db: Session = Depends(get_db)):
models = db.query(AIModel).filter(AIModel.is_enabled == True).all()
result = []
for m in models:
provider = db.query(ModelProvider).filter(ModelProvider.id == m.provider_id).first()
result.append({
"id": m.id,
"name": m.name,
"provider_id": m.provider_id,
"provider_name": provider.name if provider else "",
"status": m.status,
"price_per_million_tokens": float(m.price_per_million_tokens or 0),
"is_enabled": m.is_enabled,
"is_default": bool(m.is_default) if hasattr(m, 'is_default') else False,
"total_calls": m.total_calls or 0,
"total_tokens": m.total_tokens or 0,
"created_at": str(m.created_at) if m.created_at else None,
})
# 默认模型排在最前面
result.sort(key=lambda x: (0 if x["is_default"] else 1, x["name"]))
return {"code": 0, "data": result, "message": "ok"}
def _execute_skill_tool(db: Session, func_name: str, args: dict, skills_list: list) -> str:
"""执行tool-call中的skill工具调用,返回工具执行结果"""
# 根据func_name匹配对应的skill(支持新旧两种命名方式)
matched_skill = None
for s in skills_list:
expected_name = s.skill_name.replace(" ", "_").replace("-", "_").lower()
if func_name == expected_name or func_name == f"skill_{s.id[:8]}":
matched_skill = s
break
skill_name = matched_skill.skill_name if matched_skill else func_name
input_text = args.get("input", "")
# 根据skill类型生成模拟执行结果
skill_type = matched_skill.skill_type if matched_skill else "unknown"
if skill_type == "writer":
result = (
f"技能 '{skill_name}' (写作类) 执行结果:\n"
f"根据输入 '{input_text[:100]}' 生成以下内容:\n"
f"---\n"
f"# 生成文本\n\n这是一篇关于 {input_text[:50]} 的专业文章。"
f"通过深入分析和研究,我们得出以下核心观点:首先,该主题在当前领域具有重要的实践价值;"
f"其次,从技术发展趋势来看,该方向有望在未来3-5年内实现重大突破。"
f"\n\n## 关键建议\n1. 加强基础研究投入\n2. 建立标准化流程\n3. 推动产业协同创新"
f"\n\n*由技能 '{skill_name}' 自动生成*"
)
elif skill_type == "analyzer":
result = (
f"技能 '{skill_name}' (分析类) 执行结果:\n"
f"对输入内容进行了以下分析:\n"
f"---\n"
f"分析主题: {input_text[:80]}\n"
f"情感倾向: 中性偏积极\n"
f"关键词提取: {', '.join([w for w in input_text[:80].replace(',', ',').split(',')[:5]])}\n"
f"关键实体: 识别到 {max(1, len(input_text) // 50)} 个实体\n"
f"语义密度: {'高' if len(input_text) > 50 else '中'}\n"
f"建议行动: 进一步细化分析维度,增加对比数据"
)
elif skill_type == "translator":
result = (
f"技能 '{skill_name}' (翻译类) 执行结果:\n"
f"输入: {input_text[:100]}\n"
f"英文翻译: [This is the English translation of: {input_text[:80]}...]"
f"\n日文翻译: [これは次の内容の日本語訳です: {input_text[:50]}...]"
)
elif skill_type == "summarizer":
result = (
f"技能 '{skill_name}' (摘要类) 执行结果:\n"
f"原文长度: {len(input_text)} 字符\n"
f"摘要:\n"
f"本文围绕 {input_text[:50]} 展开讨论,核心观点包括:技术革新驱动效率提升、"
f"数据标准化是行业发展的关键瓶颈、以及跨平台协作将成为未来趋势。"
f"\n关键数字: 预计增长率为15-20%,实施周期为6-12个月。"
)
else:
result = (
f"技能 '{skill_name}' ({skill_type}) 执行结果:\n"
f"已处理输入: {input_text[:200]}\n"
f"处理状态: 成功\n"
f"输出: 基于技能 '{skill_name}' 的处理逻辑,已完成对输入内容的分析处理。"
f"建议结合具体业务场景进一步优化结果。"
)
# 更新skill使用计数
if matched_skill:
matched_skill.use_count = (matched_skill.use_count or 0) + 1
matched_skill.last_used_at = datetime.now()
return result