feat(phase8 #92 D8.5-BE): canonical UIMessage chat history + runtime_kind discriminator#1706
Merged
Merged
Conversation
…kind discriminator Phase 8 task #92 (D8.5-BE) — first-cut backend migration of the non-agent chat path to the canonical ``UIMessage`` shape, scoped per architect msg=01918929 + Weston msg=df87fe24 + earayu2 msg=f20d5034 hard-cut acceptance: The inventory revealed the production "non-agent chat path" the original D8.5 design assumed has already converged on the agent runtime (``chat_completion_service.openai_chat_completions`` already delegates to ``runtime_manager.turn_service.create_or_get_turn`` and ``ChatService.create_chat`` rejects non-AGENT bots). So the actual #92 work is A+B+C only — adding the discriminator column for future non-agent paths and migrating the user-visible chat history shape to canonical UIMessage. The translator extension (``chat.text.delta`` / ``chat.completed``) and the ``StoredChatMessagePart`` / ``RedisChatMessageHistory`` deletion are deferred per architect / Weston canonical lock. Changes: A. ``runtime_kind`` discriminator on ``agent_message`` table - ``aperag/domains/agent_runtime/db/models.py``: new ``runtime_kind: str`` ORM column with values ``agent_runtime`` / ``direct_chat`` / ``rag_chat`` (mutually exclusive enum); existing rows backfill via ``server_default="agent_runtime"``. ``role`` keeps speaker semantics independent of the runtime that produced the message. - ``aperag/migration/versions/...c8f2d34a51e7_add_agent_message_runtime_kind.py``: additive migration; downgrade drops the column. B. ``ChatService._build_v3_chat_history`` rewrite - Returns ``list[AgentTurnSnapshot]`` (one snapshot per assistant turn) instead of the legacy ``list[list[ChatMessage]]`` shape. - Reuses ``snapshot_assembler.assemble_parts_from_artifacts`` (the #90 D8.4d projection) so historical turns expose the same ``UIMessagePart`` shape the FE consumes from the live SSE stream (D8 §2 wire/at-rest byte-equal). - ``error_text`` for FAILED / CANCELLED turns surfaces an ``error_summary`` artifact's message, falling back to ``turn.error_message`` — mirrors the snapshot endpoint contract. - The turn's user query lives at ``input_text`` on the snapshot envelope (rather than as a separate ``role=human`` ChatMessage) so the FE renders user/assistant from a single object per turn. - Legacy ``_extract_artifact_text`` / ``_extract_references`` / ``_map_reference_item`` / ``_artifact_type_value`` / ``_coerce_timestamp`` helpers are retired alongside the legacy shape. C. ``ChatDetails.history`` schema - ``aperag/domains/conversation/schemas.py``: ``history`` is now ``Optional[list[AgentTurnSnapshot]]`` with explicit description citing D8 §2 byte-equal canonical and the new shape. - The ``conversation.schemas`` ↔ ``agent_runtime.uimessage`` ↔ ``agent_runtime.schemas`` ↔ ``conversation.schemas`` cycle is broken via ``TYPE_CHECKING`` import + a module-level ``ChatDetails.model_rebuild()`` hook at the bottom of ``conversation/schemas.py``. Pydantic resolves the forward ref at load time so the OpenAPI schema is fully populated. - ``aperag/domains/agent_runtime/uimessage.py``: ``AgentTurnSnapshot`` gains ``runtime_kind: RuntimeKind`` (default ``"agent_runtime"``) and ``input_text: Optional[str]`` so historical turns can render the user query without a separate envelope round-trip. - ``TurnService.get_turn_snapshot`` writes both new fields on the live snapshot endpoint so live and historical reload paths match. D. (deferred) Translator extension for ``chat.text.delta`` / ``chat.completed`` and ``StoredChatMessagePart`` / ``RedisChatMessageHistory`` deletion stay out of #92 per Weston msg=df87fe24 / PM msg=01918929. The non-agent live path the extension would have served does not exist in the current codebase; reintroducing it is a feature task, not a refactor. Tests: - ``tests/unit_test/chat/test_chat_service.py`` rewritten: * ``test_get_chat_returns_canonical_uimessage_history`` pins the new shape (snapshot per turn with text + source-url + data-citation parts, runtime_kind, input_text) * ``test_get_chat_history_surfaces_error_text_for_failed_turn`` pins the error_text contract for FAILED turns * ``test_get_chat_history_does_not_expose_legacy_chatmessage_shape`` regression-guard against revert to ``list[list[ChatMessage]]`` - ``tests/unit_test/agent_runtime/test_agent_runtime_v3.py`` updated to import ``AgentTurnSnapshot`` from ``agent_runtime.uimessage`` (the back-compat re-export through ``agent_runtime.schemas`` was retired to break the new cycle). Per D10 §G hard gate 1 (comprehensive grep sweep) ran across ``aperag/`` + ``tests/unit_test/`` + ``tests/e2e_http/hurl/`` + ``tests/e2e_http/scripts/``: only the FE ``web/src/components/chat/chat-messages.tsx`` reads ``chat.history`` in the old shape — that is the explicit hand-off seam for #93 huangheng (per architect msg=6e53a7c4). Gates: full unit suite 833 / 29 skip / 0 fail; ruff check + format clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 8 task #92 (D8.5-BE) — first-cut backend migration for non-agent bot path. Per architect msg=01918929 + Weston msg=df87fe24 + earayu2 msg=f20d5034 hard-cut, scope locked at A+B+C only. The translator extension and legacy-class deletion are explicitly deferred (architect canonical: don't build dead-code for a non-agent live path that doesn't exist in current code; let the future feature task handle that, and #80 owns the deletion).
A.
runtime_kinddiscriminator onagent_messageAgentMessagewith valuesagent_runtime/direct_chat/rag_chat(mutually exclusive enum)agent_runtimeso existing rows backfill (no data migration step)rolekeeps speaker semantics (user/assistant/system) independent of runtime kind, per Weston msg=94dac98ac8f2d34a51e7(down_revision=84fac9e3d8c2) — additive; downgrade drops the columnB.
ChatService._build_v3_chat_historyrewritelist[AgentTurnSnapshot](one snapshot per assistant turn) instead of legacylist[list[ChatMessage]]snapshot_assembler.assemble_parts_from_artifacts(chore: fix quantize failed #90 D8.4d projection) so historical turns expose the sameUIMessagePartshape the FE consumes from the live SSE stream — D8 §2 wire/at-rest byte-equalerror_textfor FAILED / CANCELLED turns surfaces anerror_summaryartifact's message, falling back toturn.error_message(mirrors snapshot endpoint contract from chore: fix quantize failed #90)input_texton the snapshot envelope so user/assistant render from a single object per turn_extract_artifact_text/_extract_references/_map_reference_item/_artifact_type_value/_coerce_timestamp) retired alongside the legacy shapeC.
ChatDetails.historyschemaOptional[list[AgentTurnSnapshot]]conversation.schemas↔agent_runtime.uimessage↔agent_runtime.schemas↔conversation.schemasresolved viaTYPE_CHECKINGimport + module-levelChatDetails.model_rebuild()hookAgentTurnSnapshotgainsruntime_kind(default"agent_runtime") andinput_textso live + historical share the same envelope shape;TurnService.get_turn_snapshotwrites both fields on the live snapshot endpointD. Deferred (out of #92 scope)
chat.text.delta/chat.completedenvelope types — non-agent live SSE path doesn't exist; reintroducing it is a feature taskStoredChatMessagePart/RedisChatMessageHistorydeletion — Weston msg=df87fe24 / PM msg=01918929 → owned by [Features] on premise deployment #80 post-soakTests
test_get_chat_returns_canonical_uimessage_historypins the new shape (snapshot per turn with text + source-url + data-citation parts, runtime_kind, input_text, timeline_cursor)test_get_chat_history_surfaces_error_text_for_failed_turnpins error_text contract for FAILED turns (artifact-preferred → turn.error_message fallback)test_get_chat_history_does_not_expose_legacy_chatmessage_shaperegression-guard against revert tolist[list[ChatMessage]]test_agent_runtime_v3.pyimport path updated (AgentTurnSnapshotnow imported fromuimessagedirectly; back-compat re-export throughschemasretired to break cycle)Per D10 §G hard gate 1 (comprehensive grep sweep) ran across
aperag/+tests/unit_test/+tests/e2e_http/hurl/+tests/e2e_http/scripts/. Onlyweb/src/components/chat/chat-messages.tsxreadschat.historyin the old shape — that's the explicit hand-off seam for #93 huangheng (per architect msg=6e53a7c4).Hand-off for #93 huangheng (FE)
Per huangheng's questions in msg=4017bc0c:
ChatDetails.historyshape:list[AgentTurnSnapshot](each snapshot hasturn_id/chat_id/runtime_kind/role: "assistant"/status/parts: UIMessagePart[]/error_text?/timeline_cursor/input_text/ timestamps)getBotChatendpoint: unchanged —GET /api/v2/bots/{bot_id}/chats/{chat_id}still returnsChatDetailsAgentTurnSnapshotshape (now withruntime_kinddiscriminator). The endpoint stays at/api/v2/agent/chats/{cid}/turns/{tid}for now; a non-agent specific endpoint is not needed because the production non-agent live path doesn't existruntime_kindin FE: surfaced for transparency but FE need not branch on it for [Features] store the celery task result to a backend #93 — historical render uses parts directlyturn_id: yes, fromagent_turntable (existing)Test plan
pytest tests/unit_test/agent_runtime/ tests/unit_test/chat/ -q→ 139 passedpytest tests/unit_test --deselect concurrent_control flake -q→ 833 passed / 29 skip / 0 failruff check→ cleanruff format --check→ clean (448 files)yarn api:v2:types) — yarn not available in BE worktree; [Features] store the celery task result to a backend #93 huangheng will regen as part of FE migrationPhase 8 D8.x Phase A + B + #90 + #92 status
51137301e290488bbd4052d563a9d522f03516623195d18b3f9303cd🤖 Generated with Claude Code