refactor(core): consolidate backend raw responses onto mot.raw namespace - #1329
Conversation
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>
|
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
left a comment
There was a problem hiding this comment.
this seems like a reasonable change and continues to bring much needed structure to mots; thank you
4403d3e
Pull Request
Issue
Fixes #1215
Description
Replaces the per-backend bag of provider-coupled
mot._metakeys with a typedmot.raw: RawProviderResponsenamespace — a sibling tomot.generation.Each backend used to stuff its native SDK response into
mot._metaunder 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_usagesiblings)._metaisCBlock-level metadata, so consumers reading these values (e.g.chat.py:_parse) had no signal they were opting into provider-coupled access. A typedmot.rawmakes that escape hatch explicit and leaves_metaforCBlock-level metadata.What changed
RawProviderResponse(provider, response, streamed_chunks)inmellea/core/base.py, exported frommellea.core. Carried throughModelOutputThunk.__init__,__copy__(shallow),__deepcopy__(deep), and_copy_from(reference), matchingmot.generationsemantics.processing/post_processing) and the raw path (_generate_from_raw). Each setsmot.raw.response(andmot.raw.streamed_chunksfor streaming) plusmot.raw.provider.chat.py:_parsedispatches onmot.raw.providerinstead of probing_metakeys. The Ollama role-recovery branch is preserved (Ollama can returnrole="tool").cli/serve/utils.py:extract_finish_reasonalso migrated to switch onmot.raw.provider.oai_chat_response_choiceremoved. It was redundant with the choice already inside the response; tool extraction now reads the response directly per its per-path shape.oai_streaming_usage,litellm_streaming_usage) is no longer stashed in_meta; it is written straight tomot.generation.usagewhen the usage chunk arrives.Reviewer callouts
_metato become a read-only deprecation shim. We chose not to add one: the_metaprovider 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 returnsNonerather than warning.chat_completion_delta_mergereturns). Every consumer that readsmot.raw.responsefor OpenAI-shaped backends detects which shape it has ("choices" in response): the backends'post_processingtool extraction,cli/serve/utils.py:extract_finish_reason, andchat.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.responseis alwaysdict | Nonefor the OpenAI-shaped backends. All three (openai, litellm, watsonx) store a dict on both paths (chunk.model_dump()/ the merged-delta dict / the WatsonXchunkdict), sopost_processingreadsusageviaassert response is not None+response.get("usage")without a non-dict fallback. The oldgetattr(response, "usage", None)guard protected a non-dict case that cannot occur on these paths.oai_*keys, not awatsonx_*namespace. The migration neutralizes that drift — all backends now usemot.raw.litellm_full_responsewas undocumented in the issue body but existed in code. It andlitellm_chat_responseare folded into a singlemot.raw.responseholding the top-level shape._parsefallback retained. HF'smot.raw.responseis aGenerateDecoderOnlyOutput(token tensors) with no role/content surface beyondmot.value, so theMessage(role="assistant", content=mot.value)fallback stays unchanged from before.mot.generation.providerandmot.raw.providerduplication is intentional: a consumer readingmot.rawshouldn't have to also reach intomot.generationto learn the provider that determinesresponse's shape.Testing
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.audit_coverage.py --quality --fail-on-quality --threshold 100) — 100% coverage, 0 issues (RawProviderResponseis new in__all__).grep -rn '_meta\[' mellea/— no provider keys remain (onlyCBlockconstructor/repr).Attribution
Adding a new component, requirement, sampling strategy, or tool?