Skip to content

refactor(core): consolidate backend raw responses onto mot.raw namespace - #1329

Merged
ajbozarth merged 2 commits into
generative-computing:mainfrom
ajbozarth:refactor/1215-backend-raw-mot
Jun 24, 2026
Merged

refactor(core): consolidate backend raw responses onto mot.raw namespace#1329
ajbozarth merged 2 commits into
generative-computing:mainfrom
ajbozarth:refactor/1215-backend-raw-mot

Conversation

@ajbozarth

@ajbozarth ajbozarth commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Issue

Fixes #1215

Description

Replaces the per-backend bag of provider-coupled mot._meta keys with a typed mot.raw: RawProviderResponse namespace — a sibling to mot.generation.

Each backend used to stuff its native SDK response into mot._meta under a different key (chat_response, oai_chat_response, oai_chat_response_choice, litellm_chat_response, litellm_full_response, oai_completion_response, generate_response, hf_output, plus *_streamed / *_streaming_usage siblings). _meta is CBlock-level metadata, so consumers reading these values (e.g. chat.py:_parse) had no signal they were opting into provider-coupled access. A typed mot.raw makes that escape hatch explicit and leaves _meta for CBlock-level metadata.

What changed

  • New type RawProviderResponse(provider, response, streamed_chunks) in mellea/core/base.py, exported from mellea.core. Carried through ModelOutputThunk.__init__, __copy__ (shallow), __deepcopy__ (deep), and _copy_from (reference), matching mot.generation semantics.
  • All five backends migrated (ollama, openai, watsonx, litellm, huggingface) on both the chat path (processing / post_processing) and the raw path (_generate_from_raw). Each sets mot.raw.response (and mot.raw.streamed_chunks for streaming) plus mot.raw.provider.
  • chat.py:_parse dispatches on mot.raw.provider instead of probing _meta keys. The Ollama role-recovery branch is preserved (Ollama can return role="tool").
  • cli/serve/utils.py:extract_finish_reason also migrated to switch on mot.raw.provider.
  • oai_chat_response_choice removed. It was redundant with the choice already inside the response; tool extraction now reads the response directly per its per-path shape.
  • Streaming usage (oai_streaming_usage, litellm_streaming_usage) is no longer stashed in _meta; it is written straight to mot.generation.usage when the usage chunk arrives.

Reviewer callouts

  • No deprecation shim. The issue body called for _meta to become a read-only deprecation shim. We chose not to add one: the _meta provider keys are private (underscored) and undocumented, and the project's convention is to run a deprecation cycle only for public surface. Reading a removed key now returns None rather than warning.
  • Per-path response shape is preserved, not unified. Non-streaming stores the full top-level response dict; streaming stores the already-merged choice-level dict (what chat_completion_delta_merge returns). Every consumer that reads mot.raw.response for OpenAI-shaped backends detects which shape it has ("choices" in response): the backends' post_processing tool extraction, cli/serve/utils.py:extract_finish_reason, and chat.py:_parse (both the tool and non-tool branches). Unifying these to a single shape is a real cleanup but a behavior change, so it is deferred (see refactor(backends): unify mot.raw.response shape across streaming and non-streaming #1330).
  • mot.raw.response is always dict | None for the OpenAI-shaped backends. All three (openai, litellm, watsonx) store a dict on both paths (chunk.model_dump() / the merged-delta dict / the WatsonX chunk dict), so post_processing reads usage via assert response is not None + response.get("usage") without a non-dict fallback. The old getattr(response, "usage", None) guard protected a non-dict case that cannot occur on these paths.
  • WatsonX wrote the oai_* keys, not a watsonx_* namespace. The migration neutralizes that drift — all backends now use mot.raw.
  • litellm_full_response was undocumented in the issue body but existed in code. It and litellm_chat_response are folded into a single mot.raw.response holding the top-level shape.
  • HuggingFace _parse fallback retained. HF's mot.raw.response is a GenerateDecoderOnlyOutput (token tensors) with no role/content surface beyond mot.value, so the Message(role="assistant", content=mot.value) fallback stays unchanged from before.
  • mot.generation.provider and mot.raw.provider duplication is intentional: a consumer reading mot.raw shouldn't have to also reach into mot.generation to learn the provider that determines response's shape.

Testing

  • Tests added to the respective file if code was changed
  • New code has 100% coverage if code was added
  • Ensure existing tests and github automation passes (a maintainer will kick off the github automation when the rest of the PR is populated)

Local verification:

  • uv run pytest test/core/test_base.py test/backends/test_ollama_unit.py test/backends/test_openai_unit.py test/backends/test_litellm_thinking.py test/backends/test_huggingface_unit.py test/stdlib/components/test_chat.py test/cli/test_serve_utils.py -m "not qualitative" — all pass.
  • ruff format --check ., ruff check ., mypy . — clean.
  • Docstring quality gate (audit_coverage.py --quality --fail-on-quality --threshold 100) — 100% coverage, 0 issues (RawProviderResponse is new in __all__).
  • grep -rn '_meta\[' mellea/ — no provider keys remain (only CBlock constructor/repr).

Attribution

  • AI coding assistants used

Adding a new component, requirement, sampling strategy, or tool?

  • Component
  • Requirement
  • Sampling Strategy
  • Tool

Replace the per-backend bag of provider-coupled `mot._meta` keys
(`chat_response`, `oai_chat_response`, `litellm_chat_response`,
`hf_output`, etc.) with a typed `mot.raw: RawProviderResponse` namespace,
a sibling to `mot.generation`.

- Add `RawProviderResponse` dataclass (provider/response/streamed_chunks)
  to mellea/core/base.py; wire into ModelOutputThunk `__init__`,
  `__copy__`, `__deepcopy__`, `_copy_from`; export from mellea.core.
- Migrate all five backends (ollama, openai, watsonx, litellm,
  huggingface) on both the chat path and `_generate_from_raw`.
- Drop the redundant `oai_chat_response_choice` key; tool extraction
  reads the response directly per its per-path shape.
- Record streaming token usage straight to `mot.generation.usage`
  instead of a transient `_meta` key.
- Switch `chat.py:_parse` and `cli/serve/utils.py:extract_finish_reason`
  to dispatch on `mot.raw.provider`.

No deprecation shim: the `_meta` keys were private and undocumented, so
following project precedent only public surface gets a deprecation cycle.

Closes generative-computing#1215.

Assisted-by: Claude Code
Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
@ajbozarth

Copy link
Copy Markdown
Contributor Author

I opened #1330 and #1331 as follow up issues.

#1330 in particular could be argued that it belongs in this PR, but I left it out in my first pass. @jakelorocco if you think I should include that refactor here I'll add it, or if you think that behavior is "as intended" I can close it.

Similarly if #1331 is "behaves as intended" I can close it. I wanted to include that fix in my work here like I did for usage in my metrics work, but Claude specifically called out the kv_cache as non-trivial to cleanly slice into MOTs

@jakelorocco

Copy link
Copy Markdown
Contributor

I opened #1330 and #1331 as follow up issues.

#1330 in particular could be argued that it belongs in this PR, but I left it out in my first pass. @jakelorocco if you think I should include that refactor here I'll add it, or if you think that behavior is "as intended" I can close it.

Similarly if #1331 is "behaves as intended" I can close it. I wanted to include that fix in my work here like I did for usage in my metrics work, but Claude specifically called out the kv_cache as non-trivial to cleanly slice into MOTs

These both seem reasonable to defer. I marked #1331 as low priority because I don't think the raw endpoints are getting a lot of use right now.

@jakelorocco jakelorocco left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a reasonable change and continues to bring much needed structure to mots; thank you

@ajbozarth
ajbozarth enabled auto-merge June 24, 2026 17:27
@ajbozarth
ajbozarth added this pull request to the merge queue Jun 24, 2026
Merged via the queue into generative-computing:main with commit 4403d3e Jun 24, 2026
12 of 13 checks passed
@ajbozarth
ajbozarth deleted the refactor/1215-backend-raw-mot branch June 24, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(core): consolidate backend raw responses onto mot.raw namespace

2 participants