feat(agent,report): 会话持久化与画像注入、报告反馈与预约留资——PM/开发双Agent迭代1-3
CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s
CI / lint / typecheck / import-linter / tests / secrets (push) Canceled after 0s
- 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 验证 画像注入与历史携带
This commit is contained in:
@@ -126,6 +126,10 @@ class Settings(BaseSettings):
|
||||
# --- Auth: refresh sessions (FR-ACC-05) --------------------------------
|
||||
refresh_token_ttl_seconds: int = 2_592_000 # 30 天
|
||||
|
||||
# --- Agent 对话(FR-AGT-09)--------------------------------------------
|
||||
# 对话上下文携带的最近轮数(一轮 = user + assistant 两条),默认 10。
|
||||
agent_history_turns: int = 10
|
||||
|
||||
# --- 任务奖励(FR-BILL-03/04,配置化)-----------------------------------
|
||||
# amount 全部是占位值 1:正式数值待产品在产品方案 §3.2 定稿后改 env,不改代码。
|
||||
# claim_period 决定幂等粒度(同一任务同周期只能领一次)。
|
||||
|
||||
+106
-3
@@ -1,9 +1,9 @@
|
||||
"""SQLAlchemy 2.0 ORM models.
|
||||
|
||||
Scope of this scaffold: only the tables needed to prove the vertical slice
|
||||
(架构文档 §6.2 identity / billing / task / 观测与成本). All remaining domains
|
||||
(pf_ / rp_ / kb_ / ag_ / tr_ / cp_) are intentionally absent — see
|
||||
packages/*/README.md for what still has to be built.
|
||||
(架构文档 §6.2 identity / billing / task / 观测与成本) plus the Sprint batches
|
||||
that have landed since (kb_ / rp_ / ag_). Remaining domains (pf_ / tr_ / cp_)
|
||||
are intentionally absent — see packages/*/README.md for what still has to be built.
|
||||
|
||||
Conventions enforced here (架构文档 §6.1):
|
||||
1. 表名带域前缀 (`id_` / `bl_` / `tk_` / `ob_`),为将来拆库预留边界。
|
||||
@@ -541,3 +541,106 @@ class RpShare(Base):
|
||||
)
|
||||
|
||||
__table_args__ = (UniqueConstraint("share_token", name="uq_rp_share_token"),)
|
||||
|
||||
|
||||
class RpFeedback(Base):
|
||||
"""维度级结论反馈(T-P3-21 最小可用版):准/不准 + 可选修正文本。
|
||||
|
||||
同维度重复反馈是覆盖更新(upsert),由 uq_rp_feedback_report_dimension
|
||||
兜底;认同率/修正率两项指标从本表 SQL 直接算出。
|
||||
有意裁剪:不回写报告结论、不写 L1 记忆(记忆系统未建,见迁移 0008)。"""
|
||||
|
||||
__tablename__ = "rp_feedback"
|
||||
|
||||
feedback_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
|
||||
report_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
dimension_key: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
verdict: Mapped[str] = mapped_column(String(16), nullable=False) # accurate | inaccurate
|
||||
correction: Mapped[str | None] = mapped_column(Text)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("report_id", "dimension_key", name="uq_rp_feedback_report_dimension"),
|
||||
Index("ix_rp_feedback_user_created", "user_id", "created_at"),
|
||||
)
|
||||
|
||||
|
||||
class RpReservation(Base):
|
||||
"""预约留资(T-P3-22):topic 预留通用性,首期只有 full_report。
|
||||
|
||||
uq_rp_reservation_user_topic 承载幂等:重复预约返回已有记录(created=false)。
|
||||
指标口径:预约率 = topic='full_report' 行数 ÷ 埋点 full_report.card_view
|
||||
去重曝光数;决策门槛 ≥8%(产品方案 §2.2.3)。"""
|
||||
|
||||
__tablename__ = "rp_reservation"
|
||||
|
||||
reservation_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
topic: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (UniqueConstraint("user_id", "topic", name="uq_rp_reservation_user_topic"),)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# agent 域(FR-AGT-09 / T-P4-01:会话与消息,含检索轨迹与规则命中)
|
||||
# ---------------------------------------------------------------------------
|
||||
class AgConversation(Base):
|
||||
"""对话会话。title 取首条用户消息的截断,列表页展示用。
|
||||
|
||||
保留策略(合规方案 §6:对话原文 90 天)由后续清理任务按
|
||||
``last_message_at`` 执行,表结构先把时间锚点留好。"""
|
||||
|
||||
__tablename__ = "ag_conversation"
|
||||
|
||||
conversation_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
title: Mapped[str] = mapped_column(String(64), nullable=False, default="")
|
||||
message_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
last_message_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (Index("ix_ag_conversation_user_active", "user_id", "last_message_at"),)
|
||||
|
||||
|
||||
class AgMessage(Base):
|
||||
"""单条对话消息。``retrieval_trace`` / ``rule_hits`` 是 FR-AGT-09「每条回答
|
||||
可复盘」的复盘锚点:检索与规则命中在对应步骤(架构 §7.2 ④)落地前留空。
|
||||
|
||||
``call_id`` 关联 ``ob_ai_call``,一条 assistant 消息的模型/token/成本可
|
||||
一路追到账本;user 消息没有 LLM 调用,这些字段恒 NULL。"""
|
||||
|
||||
__tablename__ = "ag_message"
|
||||
|
||||
message_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=False)
|
||||
conversation_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
role: Mapped[str] = mapped_column(String(16), nullable=False) # user / assistant
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
call_id: Mapped[int | None] = mapped_column(BigInteger)
|
||||
model: Mapped[str | None] = mapped_column(String(64))
|
||||
tokens_in: Mapped[int | None] = mapped_column(Integer)
|
||||
tokens_out: Mapped[int | None] = mapped_column(Integer)
|
||||
tokens_cached: Mapped[int | None] = mapped_column(Integer)
|
||||
latency_ms: Mapped[int | None] = mapped_column(Integer)
|
||||
cost_cents: Mapped[Decimal | None] = mapped_column(Numeric(14, 6))
|
||||
retrieval_trace: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
|
||||
rule_hits: Mapped[list[Any]] = mapped_column(JSONB, nullable=False, default=list)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_ag_message_conversation_created", "conversation_id", "created_at"),
|
||||
Index("ix_ag_message_user_created", "user_id", "created_at"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user