✨ feat: WP-05 模型网关核心——逻辑模型、流式适配与 Run 真实执行
- 迁移 7+8:logical_model(能力目录)、provider_endpoint(密钥只存环境变量名,永不落库)、turn、model_call(每 attempt 单独持久化:usage/TTFT/错误,Provider 未返回保持 null 不估算——ADR-003);message.run_id 关联 - OpenAI-compatible 流式适配器:httpx SSE 解析、标准化 StreamChunk(delta/usage/done)、transport 可注入(测试与故障注入);密钥经环境变量读取不进日志 - Run 执行器:后台执行完整生命周期——running → content.block.started → 真实 content.block.delta*(流式产生,delta_index 单调)→ content.block.completed → message.completed → 终态;assistant 消息为权威快照;ModelCall 记录 usage 与 TTFT;无可用端点时 Run 显式失败并留 error 事件(fail-closed) - post_message:响应提交后由框架 BackgroundTasks 执行 Run(显式预提交保证 Run 落库);SSE 改为增量轮询(重放 + 等待新事件至终态,30s 上限) - 测试 54 项全绿(+2):端到端 mock 流验证 ModelCall usage/TTFT/assistant 权威消息、无端点 Run failed + content.block.failed 事件
This commit is contained in:
1 parent
40221170ae
commit
b4242f3844
10 files changed
+789
-56
No files matched your search
@@ -24,7 +24,6 @@ from app.models import (
|
||||
Message,
|
||||
OutboxEvent,
|
||||
Run,
|
||||
RunEvent,
|
||||
)
|
||||
|
||||
MESSAGE_ROUTE = "/v1/conversations/{conversation_id}/messages"
|
||||
@@ -157,20 +156,19 @@ async def replay_or_execute_message(
|
||||
|
||||
run = None
|
||||
if body.start_run:
|
||||
# 垂直切片空状态闭环:不调用模型,Run 原子创建并直接进入终态 completed。
|
||||
# Run 创建为 queued,由网关执行器在后台推进至终态(WP-05)
|
||||
run = Run(
|
||||
personal_domain_id=domain_id,
|
||||
conversation_id=conversation.id,
|
||||
status="completed",
|
||||
completed_at=datetime.now(UTC),
|
||||
status="queued",
|
||||
)
|
||||
db.add(run)
|
||||
await db.flush()
|
||||
message.run_id = run.id
|
||||
|
||||
resource_ids: dict[str, Any] = {"message_id": message.id}
|
||||
if run is not None:
|
||||
resource_ids["run_id"] = run.id
|
||||
_record_run_events(db, run, message, blocks)
|
||||
db.add(
|
||||
AuditEvent(
|
||||
actor_type="user",
|
||||
@@ -203,36 +201,6 @@ async def replay_or_execute_message(
|
||||
return message, run, False
|
||||
|
||||
|
||||
def _record_run_events(
|
||||
db: AsyncSession, run: Run, message: Message, blocks: list[dict[str, Any]]
|
||||
) -> None:
|
||||
"""空状态闭环的事件流:状态事件 + 每 block completed + message.completed。
|
||||
|
||||
sequence 在 Run 内从 0 单调;事件与权威状态同事务提交(契约第 5 节)。
|
||||
delta/progress 事件在模型流式接入(WP-05)后由真实流产生,此处不伪造。
|
||||
"""
|
||||
seq = 0
|
||||
events: list[tuple[str, dict[str, Any]]] = [
|
||||
("run.status.changed", {"run_id": run.id, "status": run.status})
|
||||
]
|
||||
for index, block in enumerate(blocks):
|
||||
events.append(
|
||||
(
|
||||
"content.block.completed",
|
||||
{"run_id": run.id, "message_id": message.id, "index": index,
|
||||
"block": {"id": block["id"], "type": block["type"], "status": "completed"}},
|
||||
)
|
||||
)
|
||||
events.append(
|
||||
(
|
||||
"message.completed",
|
||||
{"run_id": run.id, "message_ids": [message.id], "final_status": run.status},
|
||||
)
|
||||
)
|
||||
for event_type, data in events:
|
||||
db.add(RunEvent(run_id=run.id, sequence=seq, type=event_type, data=data))
|
||||
seq += 1
|
||||
|
||||
|
||||
async def list_messages(
|
||||
db: AsyncSession,
|
||||
|
||||
Reference in new issue
Block a user