Files
xsl 7bcbb8d40b
CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s
feat(agent,report): 会话持久化与画像注入、报告反馈与预约留资——PM/开发双Agent迭代1-3
- agent: ag_conversation/ag_message 表(迁移0007),上下文组装注入最近报告画像摘要
  (report.profile.latest_profile_summary 经 apps 层闭包注入,同层不互调),历史带最近
  AGENT_HISTORY_TURNS=10 轮;POST/GET /agent/conversations 与消息历史接口
- agent 修复:ChatRequest.conversation_id 改 str(雪花ID超2^53,JS Number丢精度);
  _build_context 挪进 reserve commit 后同一 try,异常走 refund 兜底
- report: rp_feedback(维度反馈 upsert,认同率/修正率 SQL 可算,T-P3-21 最小版——
  L1 记忆未建,不回写结论)+ rp_reservation(预约留资幂等,T-P3-22,预约率口径
  行数/full_report.card_view 去重曝光,门槛≥8%),迁移0008
- 测试:全量 606 passed;ruff/mypy/lint-imports 全绿;E2E 真实 deepseek-v4-flash 验证
  画像注入与历史携带
2026-09-11 21:50:58 +08:00

58 lines
2.6 KiB
Python

"""上下文组装(架构 §7.2 步骤③)的纯逻辑:无 IO、无 LLM 调用,可单测。
预算分配(packages/agent/README.md 的预算表):
- 画像摘要 ≤ 500 token —— 按中文约 1.6 字符/token 保守取 800 字符上限,
摘要本身的生产侧(report 域)按 ``PROFILE_SUMMARY_MAX_CHARS`` 裁剪;
- 历史轮数默认 10 轮(``AGENT_HISTORY_TURNS``),一轮 = user + assistant 两条。
缓存纪律:画像与历史都是**逐用户逐轮变化**的内容,一律走 gateway 的
``messages`` 通道排在固定 system 前缀之后,绝不进 ``system_prefix``——
否则缓存命中单价 1/30 的红利会被画像差异完全打掉(README「两件容易踩的事」)。
"""
from __future__ import annotations
from collections.abc import Sequence
from packages.llm.types import Message
#: 画像摘要载荷上限(字符),对应 ≤ 500 token 的预算行。
PROFILE_SUMMARY_MAX_CHARS = 800
#: 画像摘要的消息壳。摘要是 JSON 素材(report 域 compose_prompt_material 的
#: 产物),需要这句说明告诉模型怎么用它;措辞保持中性,不引入新规则。
_PROFILE_MESSAGE_TEMPLATE = (
"以下是该用户最近一份美学诊断报告的画像摘要(JSON),"
"回答时据此给出贴合其条件的建议;用户未问及时不要主动复述全文。\n"
)
#: 单条历史消息的字符上限:用户输入已被 ChatRequest 卡在 2000 字符,
#: assistant 回复被提示词约束在 300 字以内,这里只做兜底截断防脏数据。
_HISTORY_MESSAGE_MAX_CHARS = 2000
def build_profile_message(profile_summary: str) -> Message:
return Message(role="system", content=_PROFILE_MESSAGE_TEMPLATE + profile_summary)
def build_context_messages(
*,
profile_summary: str | None,
history: Sequence[Message],
history_turns: int,
) -> list[Message]:
"""组装 gateway ``messages`` 通道的内容(固定前缀与当前输入由 gateway 自己拼)。
- 画像摘要在最前(无画像用户为 None 即不注入,走通用建议降级路径 FR-AGT-10);
- 历史按时间正序,只保留最近 ``history_turns`` 轮(一轮 = user+assistant 两条),
截尾不截头——越早的轮次对当前回答价值越低。
"""
messages: list[Message] = []
if profile_summary:
messages.append(build_profile_message(profile_summary))
tail = list(history)[-(history_turns * 2) :] if history_turns > 0 else []
for item in tail:
messages.append(Message(role=item.role, content=item.content[:_HISTORY_MESSAGE_MAX_CHARS]))
return messages