Files
PlantAI-rag-new/sdk/app/services/history.py
T
jerryandClaude Fable 5 8c103ea382 feat(capabilities): S1+S2 机制地基+多轮记忆(能力框架/热加载/MCP Provider/history 线程化)
- capabilities/{manifests,registry,providers,install_manager}.py + tool_runner.py:
  三层机制(注册层/调用层/安装管理层),四 Provider,热加载差分刷新
- configs/capabilities.yaml: 唯一注册入口(明文热加载面)
- api.py: QARequest+history/capabilities, /capabilities/install loopback 路由
- llm_client.py: complete() tools 透传 + complete_raw_messages() 工具调用循环支持
- pipeline.py/answer_composer.py: history 线程化(只进 Dialogue Context,检索零回归)
- sdk: services/history.py(3轮/16k预算) + routers/qa.py session_id 剥离注入
- compile_qa.sh: rsync 允许子目录 + capabilities.yaml 入 install 清单
- tooling/cap/: fake_mcp_server.py + cap_selfcheck.py 机制自检
- requirements.lock: mcp 2.1.1 + 依赖 12 包

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 13:00:35 +08:00

91 lines
3.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.
"""services/history.py —— 多轮记忆组装(S2,SDK 层)。
assemble_history(db, session_id, rounds=3, budget):
读 messages.text(用户问题 + 助手答案全文),组装 (user, assistant) 轮次对,
只保留最近 N 轮,16k 字符硬预算(超预算丢最旧轮,永远保留最近一轮完整 user);
status=streaming 排除;查询失败 → 空 history(记忆是增强,不阻塞问答)。
"""
from __future__ import annotations
from typing import Any, List
from ..config import get_settings
async def assemble_history(
db,
session_id: str,
rounds: int | None = None,
max_question_chars: int | None = None,
max_answer_chars: int | None = None,
) -> List[dict]:
"""组装 history:List[{role, content}],旧→新,不含当前这轮提问。
预算硬约束:单条 question ≤ max_question_chars、answer ≤ max_answer_chars,
总字符约 ≤16k(超预算丢最旧轮,最近轮永远保留)。
"""
settings = get_settings()
rounds = rounds if rounds is not None else settings.bff_history_rounds
max_question_chars = (
max_question_chars
if max_question_chars is not None
else settings.bff_history_max_question_chars
)
max_answer_chars = (
max_answer_chars
if max_answer_chars is not None
else settings.bff_history_max_answer_chars
)
if not session_id:
return []
try:
rows = await db.fetchall(
"SELECT role, status, text FROM messages "
"WHERE session_id = ? AND role IN ('user','assistant') "
"AND status != 'streaming' "
"ORDER BY created_at ASC, rowid ASC",
(session_id,),
)
except Exception:
return []
# 1. 倒序组装 (user, assistant) 轮次对(旧→新输出)
pairs: List[tuple] = [] # 新→旧
answer_parts: List[str] = []
for row in reversed(rows):
role = row["role"]
text = (row["text"] or "").strip()
if not text:
continue
if role == "assistant":
answer_parts.append(text)
else:
answer = "\n".join(reversed(answer_parts)) if answer_parts else ""
answer_parts = []
pairs.append((text, answer))
pairs.reverse() # 旧→新
# 2. 只保留最近 N 轮
if len(pairs) > rounds:
pairs = pairs[-rounds:]
# 3. 组装 + 16k 字符预算(超预算丢最旧轮,最近轮永远保留)
budget = 16 * 1024
history: List[dict] = []
for user_text, answer_text in pairs:
u = user_text[:max_question_chars]
a = answer_text[:max_answer_chars]
history.append({"role": "user", "content": u})
if a:
history.append({"role": "assistant", "content": a})
used = sum(len(m["content"]) for m in history)
while used > budget and history:
# 丢最旧一轮(user + 其后的 assistant),保最近轮
dropped = history.pop(0)
if dropped["role"] == "user" and history and history[0]["role"] == "assistant":
history.pop(0)
used = sum(len(m["content"]) for m in history)
if not history:
break
return history