feat: add author-pluggable extension point for provider-specific message fields - #1586
feat: add author-pluggable extension point for provider-specific message fields#1586AngeloDanducci wants to merge 1 commit into
Conversation
…age fields Signed-off-by: AngeloDanducci <angelo.danducci.ii@ibm.com>
jakelorocco
left a comment
There was a problem hiding this comment.
I like this approach; thank you! A few comments on some of the specifics.
|
|
||
| def merge_provider_fields( | ||
| base: dict[str, Any], | ||
| provider_fields: dict[str, dict[str, Any]] | None, |
There was a problem hiding this comment.
I think we should be creating a copy of this dict (either in this function or at a higher level?) similar to what we do with model options. Editing the dict once it's passed into the function should not edit an existing Message. I believe that's what's happening here in this example / test I wrote:
pf = {"openai": {"prediction": {"type": "content"}}}
msg = Message("user", "hi", provider_fields=pf)
tr = msg.format_for_llm()
print(tr.provider_fields)
print(pf)
print(tr.provider_fields == pf)
# {'openai': {'prediction': {'type': 'content'}}}
# {'openai': {'prediction': {'type': 'content'}}}
# True
pf["openai"]["prediction"]["type"] = "new"
print(tr.provider_fields)
print(pf)
print(tr.provider_fields == pf)
# {'openai': {'prediction': {'type': 'new'}}}
# {'openai': {'prediction': {'type': 'new'}}}
# True
| # a mismatched target raises). Must run after the known fields are set. | ||
| message_dict = merge_provider_fields( | ||
| message_dict, m.provider_fields, self._provider | ||
| ) |
There was a problem hiding this comment.
Ollama apparently auto validates messages and drops extra fields by default. Maybe we should include a note in the docstring that we will only raise errors if the underlying sdk does?
The SDK re-validates every message through a pydantic model:
- ollama/_client.py#L1284 — _copy_messages() does Message.model_validate({k: ... for k, v in dict(message).items() if v})
- Called from _client.py#L371 (sync chat) and #L989 (AsyncClient.chat) — the path mellea/backends/ollama.py uses. Your conversation: list[dict] never reaches the HTTP body as-is; it's rebuilt from validated Message objects.
That model doesn't opt into extras:
- ollama/_types.py#L283 — class Message(SubscriptableBaseModel), no model_config
- _types.py#L19 — SubscriptableBaseModel(BaseModel), also no model_config. Verified at runtime: Message.model_config == {}.
Pydantic's default for unknown keys is to discard them:
- Model config extra — "'ignore' (default): Ignore any extra attributes"
- Models → Extra fields
There was a problem hiding this comment.
This is the same behavior for hugging face's apply_chat_template: an unhandled key will be dropped.
| if not _matches(key): | ||
| continue | ||
| matched_any = True | ||
| for field, value in fields.items(): |
There was a problem hiding this comment.
We may want to wrap this section in a larger try-catch block. I'm not sure if there are other places this can fail, but I think if field isn't a dict here, it will raise a generic error.
There was a problem hiding this comment.
Maybe lines 74 and 91 can error here as well.
Pull Request
Issue
Fixes #1565
Description
Allows author-pluggable fields via
provider_fields.Given the exploration around the original issue, I think this gives us the most extensibility for the least amount of headache.
I chose provider-keyed passthrough instead of widening
format_for_llmas this leaks wire shape into component code and doesn't compose well with images/docs (same issue with a global serializer registry). Open to discussion though if anyone can see reasons to alter the design - ie if we think authoring custom serialization logic itself is of greater benefit/immediate need.Testing
Attribution
Adding a new component, requirement, sampling strategy, or tool?
If your PR adds or modifies one of the types below, check the matching box. A checklist of type-specific review items will be posted as a comment.
NOTE: Please ensure you have an issue that has been acknowledged by a core contributor and routed you to open a pull request against this repository. Otherwise, please open an issue before continuing with this pull request.