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:
@@ -1,9 +1,12 @@
|
||||
"""``POST /agent/chat`` — the vertical slice.
|
||||
"""``POST /agent/chat`` + 会话持久化接口(FR-AGT-09 / T-P4-01)。
|
||||
|
||||
One request exercises: JWT auth → quota reserve → LLM gateway (routing, prompt
|
||||
version, cache-prefix check, daily budget) → ``ob_ai_call`` cost row → quota
|
||||
commit/refund → unified envelope. Streaming (SSE, FR-AGT-01) is intentionally
|
||||
out of scope for the scaffold.
|
||||
commit/refund → ``ag_message`` 双行落库 → unified envelope. Streaming (SSE,
|
||||
FR-AGT-01) is intentionally out of scope for the scaffold.
|
||||
|
||||
会话接口:创建 / 列表 / 历史。chat 不带 conversation_id 时隐式建会话并在
|
||||
响应里带回;带则做归属校验(他人会话与不存在同口径 404,防 ID 遍历)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -13,7 +16,13 @@ import uuid
|
||||
from fastapi import APIRouter
|
||||
|
||||
from apps.api.deps import AgentServiceDep, CurrentUserId
|
||||
from apps.api.schemas import ChatRequest, ChatResponse, ChatUsage
|
||||
from apps.api.schemas import (
|
||||
ChatMessageItem,
|
||||
ChatRequest,
|
||||
ChatResponse,
|
||||
ChatUsage,
|
||||
ConversationResponse,
|
||||
)
|
||||
from packages.llm.errors import (
|
||||
AllProvidersFailedError,
|
||||
CachePrefixViolationError,
|
||||
@@ -22,11 +31,62 @@ from packages.llm.errors import (
|
||||
StructuredOutputError,
|
||||
)
|
||||
from packages.shared.errors import AppError, ErrorCode
|
||||
from packages.shared.models import AgConversation
|
||||
from packages.shared.response import ok
|
||||
|
||||
router = APIRouter(prefix="/agent", tags=["agent"])
|
||||
|
||||
|
||||
def _conversation_payload(conversation: AgConversation) -> ConversationResponse:
|
||||
return ConversationResponse(
|
||||
conversation_id=str(conversation.conversation_id),
|
||||
title=conversation.title,
|
||||
message_count=conversation.message_count,
|
||||
last_message_at=conversation.last_message_at,
|
||||
created_at=conversation.created_at,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/conversations", summary="创建空会话(FR-AGT-09)")
|
||||
async def create_conversation(
|
||||
user_id: CurrentUserId,
|
||||
agent: AgentServiceDep,
|
||||
) -> dict[str, object]:
|
||||
conversation = await agent.create_conversation(user_id=user_id)
|
||||
return ok(_conversation_payload(conversation).model_dump(mode="json"))
|
||||
|
||||
|
||||
@router.get("/conversations", summary="会话列表(最近活跃倒序)")
|
||||
async def list_conversations(
|
||||
user_id: CurrentUserId,
|
||||
agent: AgentServiceDep,
|
||||
) -> dict[str, object]:
|
||||
conversations = await agent.list_conversations(user_id=user_id)
|
||||
return ok([_conversation_payload(c).model_dump(mode="json") for c in conversations])
|
||||
|
||||
|
||||
@router.get("/conversations/{conversation_id}/messages", summary="会话历史(时间正序)")
|
||||
async def list_messages(
|
||||
conversation_id: int,
|
||||
user_id: CurrentUserId,
|
||||
agent: AgentServiceDep,
|
||||
) -> dict[str, object]:
|
||||
messages = await agent.list_messages(user_id=user_id, conversation_id=conversation_id)
|
||||
return ok(
|
||||
[
|
||||
ChatMessageItem(
|
||||
message_id=str(m.message_id),
|
||||
role=m.role,
|
||||
content=m.content,
|
||||
model=m.model,
|
||||
call_id=str(m.call_id) if m.call_id is not None else None,
|
||||
created_at=m.created_at,
|
||||
).model_dump(mode="json")
|
||||
for m in messages
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@router.post("/chat", summary="Single-turn styling chat (non-streaming)")
|
||||
async def chat(
|
||||
payload: ChatRequest,
|
||||
@@ -35,7 +95,12 @@ async def chat(
|
||||
) -> dict[str, object]:
|
||||
request_key = payload.request_key or uuid.uuid4().hex
|
||||
try:
|
||||
result = await agent.chat(user_id=user_id, message=payload.message, request_key=request_key)
|
||||
result = await agent.chat(
|
||||
user_id=user_id,
|
||||
message=payload.message,
|
||||
request_key=request_key,
|
||||
conversation_id=payload.conversation_id,
|
||||
)
|
||||
except DailyBudgetExceededError as exc:
|
||||
raise AppError(ErrorCode.LLM_DAILY_BUDGET_EXCEEDED) from exc
|
||||
except SceneDegradedError as exc:
|
||||
@@ -50,6 +115,7 @@ async def chat(
|
||||
|
||||
return ok(
|
||||
ChatResponse(
|
||||
conversation_id=str(result.conversation_id),
|
||||
reply=result.reply,
|
||||
provider=result.provider,
|
||||
model=result.model,
|
||||
|
||||
Reference in New Issue
Block a user