Repository navigation
refactor(core): consolidate backend raw responses onto mot.raw namespace #1215
Description
Sub-issue of #909. Design discussed in #1197 Area 1.
Summary
Each backend currently writes a different bag of provider-specific keys to mot._meta (chat_response, oai_chat_response, litellm_chat_response, hf_output, etc.). This issue replaces those writes with a typed mot.raw: RawProviderResponse namespace.
Motivation
mot._meta is CBlock surface, but every MOT also stuffs backend-native response objects into it. Consumers reading these values (e.g. chat.py:_parse) have no signal that they're opting into provider-coupled access. A typed sibling of mot.generation — mot.raw — makes the escape hatch explicit while leaving mot._meta for CBlock-level metadata.
Design
@dataclass
class RawProviderResponse:
"""Backend-native response payload. Provider-coupling escape hatch.
Anyone reading these fields is opting into provider-specific shape. Portable
consumers should read mot.value, mot.parsed_repr, mot.tool_calls, or
mot.generation instead.
"""
provider: str | None = None
response: Any | None = None # full SDK response object
streamed_chunks: list[Any] | None = None # streaming-onlymot.raw.responsereplaceschat_response/oai_chat_response/litellm_chat_response/hf_output.mot.raw.streamed_chunksreplaces the parallel*_streamedlists.*_streaming_usagekeys disappear — already covered bymot.generation.usage.oai_chat_response_choiceis dropped — always equalsoai_chat_response["choices"][0].
response is typed Any. The whole point of raw is opt-in provider coupling; portable structured access is on mot.generation.
chat.py:_parse migration
Switches on mot.raw.provider:
"ollama"→response.message.role/response.message.content(role can be"assistant"or"tool")"openai"/"watsonx"→response["choices"][0]["message"]…- HuggingFace fallback unchanged
The role-recovery branch stays — Ollama can legitimately return role="tool" (SDK docstring), so hardcoding "assistant" would silently change behavior for tool responses. (OQ2 resolution)
Migration
Single PR:
- Backends migrate writes from
_meta[<key>]tomot.raw.response/mot.raw.streamed_chunks. - Internal readers migrate (
chat.py:_parse, tests). _metabecomes a read-only deprecation shim emittingDeprecationWarningfor the old keys, for one minor release.
After one minor cycle: shim removed.
Appendix items folded in
From #1197's Appendix A, all to verify or address during this migration:
- Item 3 —
chat.py:_parseHuggingFace fallback hardcodesrole="assistant"and returns raw text without role extraction. Confirm during migration whether this fallback is still desired withmot.raw.responseavailable, or whether a richer shape can be extracted. - Item 4 —
oai_chat_response_choiceis redundant withoai_chat_response["choices"][0]. Drop in this PR (bothopenai.pyandwatsonx.pywrite it). - Item 5 —
chat.py:_parseHuggingFace tool-call branch returnsMessage(role="assistant", content=computed.value)without recovering tool-call structure. Decide whether to serializecomputed.tool_callsinstead during migration.
Out of scope
- Logits (Standardize logits in MoT #123): per ModelOutputThunk field refactor: partition fields into sub-structures #793 precedent, logits are execution metadata and belong on
mot.generation(e.g.generation.logprobs), notmot.raw. - Per-provider Union typing on
response:Anyis correct (OQ1 resolution). - Migrating raw-path
_meta["usage"]tomot.generation.usage: separate bug fix (fix(backends): unify raw-path token usage on mot.generation.usage; guard eval_count=None #1218), not Epic: ModelOutputThunk structural cleanup #909 scope.
Acceptance
-
mot.rawpopulated by all five backends (ollama, openai, watsonx, litellm, huggingface) on chat path -
mot.raw.streamed_chunkspopulated on streaming path -
chat.py:_parseswitches onmot.raw.provider -
_meta[<old key>]reads emitDeprecationWarning -
oai_chat_response_choiceremoved - Tests updated; existing behavior preserved
Sequencing
Independent of #1181. Can ship after #1181 merges (which closes #909 Area 2) but before or after Area 3 (#1216) / Area 4 (#1217).
Activity
ajbozarth commentedon Jun 19, 2026
Drift / refresh (2026-06-19)
Path and post-#1264 updates before I pick this up:
File path drift. Since the design was written, the codebase reorg moved the targets:
mellea/stdlib/base.py→mellea/core/base.pychat.py→mellea/stdlib/components/chat.pyMigration target rename (post-#1264). Backends now implement
_generate_from_raw(the publicgenerate_from_rawis a@finalwrapper). The chat path'spost_processingis unchanged. So the_metawrites to migrate live in:_postprocess_chunk/ streaming aggregation (chat path, all backends)post_processing(chat path, all backends)_generate_from_raw(raw path, all backends)Key inventory needs one addition. Current
grep -rn '_meta\[' mellea/backends/:chat_responseoai_chat_response,oai_chat_response_choice,oai_chat_response_streamed,oai_streaming_usageoai_chat_response,oai_chat_response_choice,oai_chat_response_streamed(uses the openai keys, notwatsonx_*)litellm_chat_response,litellm_chat_response_streamed,litellm_streaming_usage,litellm_full_response(← not in the issue body's list)hf_outputTwo clarifications worth folding into the PR description when this lands:
litellm_full_responseexists in the code today and is read internally by litellm's ownpost_processingfor usage extraction. Migrate it alongsidelitellm_chat_response(or scope it out explicitly).oai_*keys, not awatsonx_*namespace. The migration table in the issue's "design" section should make that explicit so reviewers don't expect a per-backend prefix.Logits coordination. PR #1261 is now actively implementing the
mot.generation.logitspath that this issue's "Out of scope" bullet predicted. Confirms the placement decision; no rebase work either way (logits go ongeneration, notraw).refactor(core): consolidate backend raw responses onto mot.raw namespace
refactor(core): consolidate backend raw responses onto mot.raw namesp…