update pay function
This commit is contained in:
BIN
__pycache__/mcp_elasticsearch.cpython-310.pyc
Normal file
BIN
__pycache__/mcp_elasticsearch.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/mcp_server.cpython-310.pyc
Normal file
BIN
__pycache__/mcp_server.cpython-310.pyc
Normal file
Binary file not shown.
@@ -69,6 +69,8 @@ class ESClient:
|
|||||||
},
|
},
|
||||||
"plan": {"type": "text"}, # 执行计划(仅 assistant)
|
"plan": {"type": "text"}, # 执行计划(仅 assistant)
|
||||||
"steps": {"type": "text"}, # 执行步骤(仅 assistant)
|
"steps": {"type": "text"}, # 执行步骤(仅 assistant)
|
||||||
|
"session_title": {"type": "text"}, # 会话标题/概述(新增)
|
||||||
|
"is_first_message": {"type": "boolean"}, # 是否是会话首条消息(新增)
|
||||||
"timestamp": {"type": "date"}, # 时间戳
|
"timestamp": {"type": "date"}, # 时间戳
|
||||||
"created_at": {"type": "date"}, # 创建时间
|
"created_at": {"type": "date"}, # 创建时间
|
||||||
}
|
}
|
||||||
@@ -105,6 +107,8 @@ class ESClient:
|
|||||||
message: str,
|
message: str,
|
||||||
plan: Optional[str] = None,
|
plan: Optional[str] = None,
|
||||||
steps: Optional[str] = None,
|
steps: Optional[str] = None,
|
||||||
|
session_title: Optional[str] = None,
|
||||||
|
is_first_message: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
保存聊天消息
|
保存聊天消息
|
||||||
@@ -118,6 +122,8 @@ class ESClient:
|
|||||||
message: 消息内容
|
message: 消息内容
|
||||||
plan: 执行计划(可选)
|
plan: 执行计划(可选)
|
||||||
steps: 执行步骤(可选)
|
steps: 执行步骤(可选)
|
||||||
|
session_title: 会话标题(可选,通常在首条消息时设置)
|
||||||
|
is_first_message: 是否是会话首条消息
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
文档ID
|
文档ID
|
||||||
@@ -136,6 +142,8 @@ class ESClient:
|
|||||||
"message_embedding": embedding if embedding else None,
|
"message_embedding": embedding if embedding else None,
|
||||||
"plan": plan,
|
"plan": plan,
|
||||||
"steps": steps,
|
"steps": steps,
|
||||||
|
"session_title": session_title,
|
||||||
|
"is_first_message": is_first_message,
|
||||||
"timestamp": datetime.now(),
|
"timestamp": datetime.now(),
|
||||||
"created_at": datetime.now(),
|
"created_at": datetime.now(),
|
||||||
}
|
}
|
||||||
@@ -157,10 +165,10 @@ class ESClient:
|
|||||||
limit: 返回数量
|
limit: 返回数量
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
会话列表,每个会话包含:session_id, last_message, last_timestamp
|
会话列表,每个会话包含:session_id, title, last_message, last_timestamp
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 聚合查询:按 session_id 分组,获取每个会话的最后一条消息
|
# 聚合查询:按 session_id 分组,获取每个会话的最后一条消息和标题
|
||||||
query = {
|
query = {
|
||||||
"query": {
|
"query": {
|
||||||
"term": {"user_id": user_id}
|
"term": {"user_id": user_id}
|
||||||
@@ -180,7 +188,15 @@ class ESClient:
|
|||||||
"top_hits": {
|
"top_hits": {
|
||||||
"size": 1,
|
"size": 1,
|
||||||
"sort": [{"timestamp": {"order": "desc"}}],
|
"sort": [{"timestamp": {"order": "desc"}}],
|
||||||
"_source": ["message", "timestamp", "message_type"]
|
"_source": ["message", "timestamp", "message_type", "session_title"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# 获取首条消息(包含标题)
|
||||||
|
"first_message": {
|
||||||
|
"top_hits": {
|
||||||
|
"size": 1,
|
||||||
|
"sort": [{"timestamp": {"order": "asc"}}],
|
||||||
|
"_source": ["session_title", "message"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,11 +209,21 @@ class ESClient:
|
|||||||
|
|
||||||
sessions = []
|
sessions = []
|
||||||
for bucket in result["aggregations"]["sessions"]["buckets"]:
|
for bucket in result["aggregations"]["sessions"]["buckets"]:
|
||||||
session_data = bucket["last_message_content"]["hits"]["hits"][0]["_source"]
|
last_msg = bucket["last_message_content"]["hits"]["hits"][0]["_source"]
|
||||||
|
first_msg = bucket["first_message"]["hits"]["hits"][0]["_source"]
|
||||||
|
|
||||||
|
# 优先使用 session_title,否则使用首条消息的前30字符
|
||||||
|
title = (
|
||||||
|
last_msg.get("session_title") or
|
||||||
|
first_msg.get("session_title") or
|
||||||
|
first_msg.get("message", "")[:30]
|
||||||
|
)
|
||||||
|
|
||||||
sessions.append({
|
sessions.append({
|
||||||
"session_id": bucket["key"],
|
"session_id": bucket["key"],
|
||||||
"last_message": session_data["message"],
|
"title": title,
|
||||||
"last_timestamp": session_data["timestamp"],
|
"last_message": last_msg["message"],
|
||||||
|
"last_timestamp": last_msg["timestamp"],
|
||||||
"message_count": bucket["doc_count"],
|
"message_count": bucket["doc_count"],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
169
mcp_server.py
169
mcp_server.py
@@ -1845,6 +1845,24 @@ class MCPAgentIntegrated:
|
|||||||
for tool in tools
|
for tool in tools
|
||||||
])
|
])
|
||||||
|
|
||||||
|
# 获取当前时间信息
|
||||||
|
from datetime import datetime
|
||||||
|
now = datetime.now()
|
||||||
|
current_time_info = f"""## 当前时间
|
||||||
|
- **日期**: {now.strftime('%Y年%m月%d日')}
|
||||||
|
- **时间**: {now.strftime('%H:%M:%S')}
|
||||||
|
- **星期**: {['周一', '周二', '周三', '周四', '周五', '周六', '周日'][now.weekday()]}
|
||||||
|
- **A股交易时间**: 上午 9:30-11:30,下午 13:00-15:00
|
||||||
|
- **当前是否交易时段**: {'是' if (now.weekday() < 5 and ((now.hour == 9 and now.minute >= 30) or (10 <= now.hour < 11) or (now.hour == 11 and now.minute <= 30) or (13 <= now.hour < 15))) else '否'}
|
||||||
|
|
||||||
|
**时间语义理解**:
|
||||||
|
- "今天/当天" = {now.strftime('%Y-%m-%d')}
|
||||||
|
- "最近/近期" = 最近 5-10 个交易日
|
||||||
|
- "短线" = 5-20 个交易日
|
||||||
|
- "中线" = 1-3 个月
|
||||||
|
- "长线" = 6 个月以上
|
||||||
|
"""
|
||||||
|
|
||||||
return f"""你是"价小前",北京价值前沿科技公司的AI投研聊天助手。
|
return f"""你是"价小前",北京价值前沿科技公司的AI投研聊天助手。
|
||||||
|
|
||||||
## 你的人格特征
|
## 你的人格特征
|
||||||
@@ -1854,6 +1872,8 @@ class MCPAgentIntegrated:
|
|||||||
- **性格**: 专业、严谨、友好,擅长用简洁的语言解释复杂的金融概念
|
- **性格**: 专业、严谨、友好,擅长用简洁的语言解释复杂的金融概念
|
||||||
- **服务宗旨**: 帮助投资者做出更明智的投资决策,提供数据驱动的研究支持
|
- **服务宗旨**: 帮助投资者做出更明智的投资决策,提供数据驱动的研究支持
|
||||||
|
|
||||||
|
{current_time_info}
|
||||||
|
|
||||||
## 可用工具
|
## 可用工具
|
||||||
|
|
||||||
{tools_desc}
|
{tools_desc}
|
||||||
@@ -1950,15 +1970,30 @@ class MCPAgentIntegrated:
|
|||||||
|
|
||||||
只返回JSON,不要其他内容。"""
|
只返回JSON,不要其他内容。"""
|
||||||
|
|
||||||
async def create_plan(self, user_query: str, tools: List[dict]) -> ExecutionPlan:
|
async def create_plan(self, user_query: str, tools: List[dict], chat_history: List[dict] = None) -> ExecutionPlan:
|
||||||
"""阶段1: 使用 Kimi 创建执行计划(带思考过程)"""
|
"""阶段1: 使用 Kimi 创建执行计划(带思考过程和历史上下文)"""
|
||||||
logger.info(f"[Planning] Kimi开始制定计划: {user_query}")
|
logger.info(f"[Planning] Kimi开始制定计划: {user_query}")
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{"role": "system", "content": self.get_planning_prompt(tools)},
|
{"role": "system", "content": self.get_planning_prompt(tools)},
|
||||||
{"role": "user", "content": user_query},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# 添加会话历史(多轮对话上下文)
|
||||||
|
if chat_history:
|
||||||
|
# 限制历史消息数量,避免 context 过长
|
||||||
|
recent_history = chat_history[-10:] # 最近10条消息
|
||||||
|
for msg in recent_history:
|
||||||
|
role = "user" if msg.get("message_type") == "user" else "assistant"
|
||||||
|
content = msg.get("message", "")
|
||||||
|
# 截断过长的历史消息
|
||||||
|
if len(content) > 500:
|
||||||
|
content = content[:500] + "..."
|
||||||
|
messages.append({"role": role, "content": content})
|
||||||
|
logger.info(f"[Planning] 添加了 {len(recent_history)} 条历史消息到上下文")
|
||||||
|
|
||||||
|
# 添加当前用户问题
|
||||||
|
messages.append({"role": "user", "content": user_query})
|
||||||
|
|
||||||
# 使用 Kimi 思考模型
|
# 使用 Kimi 思考模型
|
||||||
response = self.kimi_client.chat.completions.create(
|
response = self.kimi_client.chat.completions.create(
|
||||||
model=self.kimi_model,
|
model=self.kimi_model,
|
||||||
@@ -2229,13 +2264,16 @@ class MCPAgentIntegrated:
|
|||||||
user_query: str,
|
user_query: str,
|
||||||
tools: List[dict],
|
tools: List[dict],
|
||||||
tool_handlers: Dict[str, Any],
|
tool_handlers: Dict[str, Any],
|
||||||
|
chat_history: List[dict] = None,
|
||||||
) -> AgentResponse:
|
) -> AgentResponse:
|
||||||
"""主流程(非流式)"""
|
"""主流程(非流式)"""
|
||||||
logger.info(f"[Agent] 处理查询: {user_query}")
|
logger.info(f"[Agent] 处理查询: {user_query}")
|
||||||
|
if chat_history:
|
||||||
|
logger.info(f"[Agent] 带有 {len(chat_history)} 条历史消息")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 阶段1: Kimi 制定计划
|
# 阶段1: Kimi 制定计划(带历史上下文)
|
||||||
plan = await self.create_plan(user_query, tools)
|
plan = await self.create_plan(user_query, tools, chat_history)
|
||||||
|
|
||||||
# 阶段2: 执行工具
|
# 阶段2: 执行工具
|
||||||
step_results = await self.execute_plan(plan, tool_handlers)
|
step_results = await self.execute_plan(plan, tool_handlers)
|
||||||
@@ -2271,6 +2309,46 @@ class MCPAgentIntegrated:
|
|||||||
message=f"处理失败: {str(e)}",
|
message=f"处理失败: {str(e)}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def generate_session_title(self, user_message: str, assistant_response: str) -> str:
|
||||||
|
"""生成会话标题(简短概述),使用 DeepMoney 模型"""
|
||||||
|
try:
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "你是一个标题生成器。根据用户问题和AI回复,生成一个简短的会话标题(10-20个字)。只返回标题文本,不要任何其他内容。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": f"用户问题:{user_message[:200]}\n\nAI回复:{assistant_response[:500]}\n\n请生成一个简短的会话标题:"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# 使用 DeepMoney 模型(更轻量,适合简单任务)
|
||||||
|
response = self.deepmoney_client.chat.completions.create(
|
||||||
|
model=self.deepmoney_model,
|
||||||
|
messages=messages,
|
||||||
|
temperature=0.3,
|
||||||
|
max_tokens=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
title = response.choices[0].message.content.strip()
|
||||||
|
|
||||||
|
# 处理 DeepMoney 的 <think>...</think> 标签,只保留 </think> 之后的内容
|
||||||
|
if "</think>" in title:
|
||||||
|
title = title.split("</think>")[-1].strip()
|
||||||
|
|
||||||
|
# 清理可能的引号
|
||||||
|
title = title.strip('"\'')
|
||||||
|
# 限制长度
|
||||||
|
if len(title) > 30:
|
||||||
|
title = title[:27] + "..."
|
||||||
|
return title
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[Title] 生成标题失败: {e}")
|
||||||
|
# 降级:使用用户消息的前20个字符
|
||||||
|
return user_message[:20] + "..." if len(user_message) > 20 else user_message
|
||||||
|
|
||||||
async def process_query_stream(
|
async def process_query_stream(
|
||||||
self,
|
self,
|
||||||
user_query: str,
|
user_query: str,
|
||||||
@@ -2282,9 +2360,15 @@ class MCPAgentIntegrated:
|
|||||||
user_avatar: str = None,
|
user_avatar: str = None,
|
||||||
cookies: dict = None,
|
cookies: dict = None,
|
||||||
model_config: dict = None, # 新增:动态模型配置
|
model_config: dict = None, # 新增:动态模型配置
|
||||||
|
chat_history: List[dict] = None, # 新增:历史对话记录
|
||||||
|
is_new_session: bool = False, # 新增:是否是新会话(用于生成标题)
|
||||||
) -> AsyncGenerator[str, None]:
|
) -> AsyncGenerator[str, None]:
|
||||||
"""主流程(流式输出)- 逐步返回执行结果"""
|
"""主流程(流式输出)- 逐步返回执行结果"""
|
||||||
logger.info(f"[Agent Stream] 处理查询: {user_query}")
|
logger.info(f"[Agent Stream] 处理查询: {user_query}")
|
||||||
|
if chat_history:
|
||||||
|
logger.info(f"[Agent Stream] 带有 {len(chat_history)} 条历史消息")
|
||||||
|
if is_new_session:
|
||||||
|
logger.info(f"[Agent Stream] 这是新会话,将在完成后生成标题")
|
||||||
|
|
||||||
# 将 cookies 存储为实例属性,供工具调用时使用
|
# 将 cookies 存储为实例属性,供工具调用时使用
|
||||||
self.cookies = cookies or {}
|
self.cookies = cookies or {}
|
||||||
@@ -2309,11 +2393,26 @@ class MCPAgentIntegrated:
|
|||||||
# 阶段1: 使用选中的模型制定计划(流式,带 DeepMoney 备选)
|
# 阶段1: 使用选中的模型制定计划(流式,带 DeepMoney 备选)
|
||||||
yield self._format_sse("status", {"stage": "planning", "message": "正在制定执行计划..."})
|
yield self._format_sse("status", {"stage": "planning", "message": "正在制定执行计划..."})
|
||||||
|
|
||||||
|
# 构建消息列表(包含历史对话上下文)
|
||||||
messages = [
|
messages = [
|
||||||
{"role": "system", "content": self.get_planning_prompt(tools)},
|
{"role": "system", "content": self.get_planning_prompt(tools)},
|
||||||
{"role": "user", "content": user_query},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# 添加历史对话(最近 10 轮,避免上下文过长)
|
||||||
|
if chat_history:
|
||||||
|
recent_history = chat_history[-10:] # 最近 10 条消息
|
||||||
|
for msg in recent_history:
|
||||||
|
role = "user" if msg.get("message_type") == "user" else "assistant"
|
||||||
|
content = msg.get("message", "")
|
||||||
|
# 截断过长的历史消息
|
||||||
|
if len(content) > 500:
|
||||||
|
content = content[:500] + "..."
|
||||||
|
messages.append({"role": role, "content": content})
|
||||||
|
logger.info(f"[Agent Stream] 已添加 {len(recent_history)} 条历史消息到上下文")
|
||||||
|
|
||||||
|
# 添加当前用户查询
|
||||||
|
messages.append({"role": "user", "content": user_query})
|
||||||
|
|
||||||
reasoning_content = ""
|
reasoning_content = ""
|
||||||
plan_content = ""
|
plan_content = ""
|
||||||
use_fallback = False
|
use_fallback = False
|
||||||
@@ -2650,6 +2749,17 @@ class MCPAgentIntegrated:
|
|||||||
"steps": [{"tool": step.tool, "arguments": step.arguments, "reason": step.reason} for step in plan.steps]
|
"steps": [{"tool": step.tool, "arguments": step.arguments, "reason": step.reason} for step in plan.steps]
|
||||||
}, ensure_ascii=False)
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
# 如果是新会话,生成会话标题
|
||||||
|
session_title = None
|
||||||
|
if is_new_session:
|
||||||
|
try:
|
||||||
|
session_title = await self.generate_session_title(user_query, final_summary)
|
||||||
|
logger.info(f"[Title] 新会话标题: {session_title}")
|
||||||
|
except Exception as title_error:
|
||||||
|
logger.error(f"[Title] 生成标题失败: {title_error}")
|
||||||
|
# 降级:使用用户消息的前 20 个字符
|
||||||
|
session_title = user_query[:20] + "..." if len(user_query) > 20 else user_query
|
||||||
|
|
||||||
es_client.save_chat_message(
|
es_client.save_chat_message(
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
@@ -2659,8 +2769,13 @@ class MCPAgentIntegrated:
|
|||||||
message=final_summary,
|
message=final_summary,
|
||||||
plan=plan_json,
|
plan=plan_json,
|
||||||
steps=steps_json,
|
steps=steps_json,
|
||||||
|
session_title=session_title, # 新会话时保存标题
|
||||||
)
|
)
|
||||||
logger.info(f"[ES] Agent 回复已保存到会话 {session_id}")
|
logger.info(f"[ES] Agent 回复已保存到会话 {session_id}")
|
||||||
|
|
||||||
|
# 如果生成了标题,通过 SSE 发送给前端
|
||||||
|
if session_title:
|
||||||
|
yield self._format_sse("session_title", {"title": session_title})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[ES] 保存 Agent 回复失败: {e}", exc_info=True)
|
logger.error(f"[ES] 保存 Agent 回复失败: {e}", exc_info=True)
|
||||||
|
|
||||||
@@ -2751,6 +2866,17 @@ async def agent_chat(request: AgentChatRequest):
|
|||||||
# ==================== 会话管理 ====================
|
# ==================== 会话管理 ====================
|
||||||
# 如果没有提供 session_id,创建新会话
|
# 如果没有提供 session_id,创建新会话
|
||||||
session_id = request.session_id or str(uuid.uuid4())
|
session_id = request.session_id or str(uuid.uuid4())
|
||||||
|
is_new_session = not request.session_id
|
||||||
|
|
||||||
|
# 获取会话历史(用于多轮对话)
|
||||||
|
chat_history = []
|
||||||
|
if not is_new_session:
|
||||||
|
try:
|
||||||
|
history = es_client.get_chat_history(session_id, limit=20) # 最近20条
|
||||||
|
chat_history = history
|
||||||
|
logger.info(f"加载会话历史: {len(chat_history)} 条消息")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取会话历史失败: {e}")
|
||||||
|
|
||||||
# 保存用户消息到 ES
|
# 保存用户消息到 ES
|
||||||
try:
|
try:
|
||||||
@@ -2761,6 +2887,7 @@ async def agent_chat(request: AgentChatRequest):
|
|||||||
user_avatar=request.user_avatar or "",
|
user_avatar=request.user_avatar or "",
|
||||||
message_type="user",
|
message_type="user",
|
||||||
message=request.message,
|
message=request.message,
|
||||||
|
is_first_message=is_new_session,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"保存用户消息失败: {e}")
|
logger.error(f"保存用户消息失败: {e}")
|
||||||
@@ -2796,14 +2923,16 @@ async def agent_chat(request: AgentChatRequest):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# 处理查询
|
# 处理查询(传入会话历史实现多轮对话)
|
||||||
response = await agent.process_query(
|
response = await agent.process_query(
|
||||||
user_query=request.message,
|
user_query=request.message,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
tool_handlers=TOOL_HANDLERS,
|
tool_handlers=TOOL_HANDLERS,
|
||||||
|
chat_history=chat_history,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 保存 Agent 回复到 ES
|
# 保存 Agent 回复到 ES
|
||||||
|
session_title = None
|
||||||
try:
|
try:
|
||||||
# 将执行步骤转换为JSON字符串
|
# 将执行步骤转换为JSON字符串
|
||||||
steps_json = json.dumps(
|
steps_json = json.dumps(
|
||||||
@@ -2814,6 +2943,11 @@ async def agent_chat(request: AgentChatRequest):
|
|||||||
# 将 plan 转换为 JSON 字符串(ES 中 plan 字段是 text 类型)
|
# 将 plan 转换为 JSON 字符串(ES 中 plan 字段是 text 类型)
|
||||||
plan_json = json.dumps(response.plan.dict(), ensure_ascii=False) if response.plan else None
|
plan_json = json.dumps(response.plan.dict(), ensure_ascii=False) if response.plan else None
|
||||||
|
|
||||||
|
# 如果是新会话,生成会话标题
|
||||||
|
if is_new_session:
|
||||||
|
session_title = await agent.generate_session_title(request.message, response.final_summary)
|
||||||
|
logger.info(f"生成会话标题: {session_title}")
|
||||||
|
|
||||||
es_client.save_chat_message(
|
es_client.save_chat_message(
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
user_id=request.user_id or "anonymous",
|
user_id=request.user_id or "anonymous",
|
||||||
@@ -2823,13 +2957,15 @@ async def agent_chat(request: AgentChatRequest):
|
|||||||
message=response.final_summary, # 使用 final_summary 而不是 final_answer
|
message=response.final_summary, # 使用 final_summary 而不是 final_answer
|
||||||
plan=plan_json, # 传递 JSON 字符串而不是字典
|
plan=plan_json, # 传递 JSON 字符串而不是字典
|
||||||
steps=steps_json,
|
steps=steps_json,
|
||||||
|
session_title=session_title,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"保存 Agent 回复失败: {e}", exc_info=True)
|
logger.error(f"保存 Agent 回复失败: {e}", exc_info=True)
|
||||||
|
|
||||||
# 在响应中返回 session_id
|
# 在响应中返回 session_id 和 title
|
||||||
response_dict = response.dict()
|
response_dict = response.dict()
|
||||||
response_dict["session_id"] = session_id
|
response_dict["session_id"] = session_id
|
||||||
|
response_dict["session_title"] = session_title
|
||||||
return response_dict
|
return response_dict
|
||||||
|
|
||||||
@app.post("/agent/chat/stream")
|
@app.post("/agent/chat/stream")
|
||||||
@@ -2872,9 +3008,21 @@ async def agent_chat_stream(chat_request: AgentChatRequest, request: Request):
|
|||||||
f"subscription_type: {user_subscription}"
|
f"subscription_type: {user_subscription}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 如果没有提供 session_id,创建新会话
|
# 判断是否是新会话
|
||||||
|
is_new_session = not chat_request.session_id
|
||||||
session_id = chat_request.session_id or str(uuid.uuid4())
|
session_id = chat_request.session_id or str(uuid.uuid4())
|
||||||
|
|
||||||
|
# ==================== 加载历史对话(多轮对话记忆)====================
|
||||||
|
chat_history = []
|
||||||
|
if not is_new_session:
|
||||||
|
try:
|
||||||
|
# 加载该会话的历史消息(最近 20 条)
|
||||||
|
history = es_client.get_chat_history(session_id, limit=20)
|
||||||
|
chat_history = history
|
||||||
|
logger.info(f"[Stream] 已加载 {len(chat_history)} 条历史消息")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[Stream] 加载历史消息失败: {e}")
|
||||||
|
|
||||||
# 保存用户消息到 ES
|
# 保存用户消息到 ES
|
||||||
try:
|
try:
|
||||||
es_client.save_chat_message(
|
es_client.save_chat_message(
|
||||||
@@ -2884,6 +3032,7 @@ async def agent_chat_stream(chat_request: AgentChatRequest, request: Request):
|
|||||||
user_avatar=chat_request.user_avatar or "",
|
user_avatar=chat_request.user_avatar or "",
|
||||||
message_type="user",
|
message_type="user",
|
||||||
message=chat_request.message,
|
message=chat_request.message,
|
||||||
|
is_first_message=is_new_session, # 标记是否为首条消息
|
||||||
)
|
)
|
||||||
logger.info(f"[ES] 用户消息已保存到会话 {session_id}")
|
logger.info(f"[ES] 用户消息已保存到会话 {session_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -2940,6 +3089,8 @@ async def agent_chat_stream(chat_request: AgentChatRequest, request: Request):
|
|||||||
user_avatar=chat_request.user_avatar,
|
user_avatar=chat_request.user_avatar,
|
||||||
cookies=cookies, # 传递 cookies 用于认证 API 调用
|
cookies=cookies, # 传递 cookies 用于认证 API 调用
|
||||||
model_config=model_config, # 传递选中的模型配置
|
model_config=model_config, # 传递选中的模型配置
|
||||||
|
chat_history=chat_history, # 传递历史对话(多轮对话记忆)
|
||||||
|
is_new_session=is_new_session, # 传递是否是新会话(用于生成标题)
|
||||||
),
|
),
|
||||||
media_type="text/event-stream",
|
media_type="text/event-stream",
|
||||||
headers={
|
headers={
|
||||||
|
|||||||
@@ -40,16 +40,22 @@ const SessionCard = ({ session, isActive, onPress }) => {
|
|||||||
<CardBody p={3}>
|
<CardBody p={3}>
|
||||||
<Flex align="start" justify="space-between" gap={2}>
|
<Flex align="start" justify="space-between" gap={2}>
|
||||||
<Box flex={1} minW={0}>
|
<Box flex={1} minW={0}>
|
||||||
<Text fontSize="sm" fontWeight="medium" color="gray.100" noOfLines={1}>
|
<Text fontSize="sm" fontWeight="medium" color="gray.100" noOfLines={2}>
|
||||||
{session.title || '新对话'}
|
{session.title || session.last_message?.substring(0, 30) || '新对话'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontSize="xs" color="gray.500" mt={1}>
|
<Text fontSize="xs" color="gray.500" mt={1}>
|
||||||
{new Date(session.created_at || session.timestamp).toLocaleString('zh-CN', {
|
{(() => {
|
||||||
month: 'numeric',
|
const dateStr = session.created_at || session.last_timestamp || session.timestamp;
|
||||||
day: 'numeric',
|
if (!dateStr) return '刚刚';
|
||||||
hour: '2-digit',
|
const date = new Date(dateStr);
|
||||||
minute: '2-digit',
|
if (isNaN(date.getTime())) return '刚刚';
|
||||||
})}
|
return date.toLocaleString('zh-CN', {
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
})()}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{session.message_count && (
|
{session.message_count && (
|
||||||
|
|||||||
Reference in New Issue
Block a user