- 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>
91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
"""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
|