Repository navigation
fix(portkey): do not drain generator content and capture image parts - #843
SAMurai-16 wants to merge 15 commits into
Conversation
Pull request dashboard statusWaiting on the author · refreshed 2026-11-01 08:52 UTC Respond to 1 review item (e.g. link a commit, explain why not, ask a follow-up):
Status above doesn't look right?
|
- Only walk materialized Sequences in _prepare_input_messages to leave generator content unconsumed for the SDK - Map image_url parts to UriPart and BlobPart via image_from_url - Add unit and integration tests covering generator content and image parts
201b09b to
a68881c
Compare
This comment has been minimized.
This comment has been minimized.
|
I bumped the opentelemetry-util-genai dependency floor to |
…or-content-and-image-parts # Conflicts: # README.md
sangkyoonnam
left a comment
There was a problem hiding this comment.
I checked 3348b084 and a local merge of it with main at a408e14e (which has #890), using an HTTP transport stub and portkey-ai 2.3.4. With content capture on, main sends "messages": [] when messages is a generator or a map(), including the async, stream=True and HTTP 400 cases. This PR sends both input messages in each case and records them in gen_ai.input.messages. The request now also carries every item from a nested content generator and from an assistant tool_calls generator. With capture off, main and this PR send the same request and record no message content.
Merging main at a408e14e conflicts in README.md and tests/requirements.oldest.txt. Main's util-genai is now 1.4b0.dev, so deps-check treats the 1.3b0.dev floor as an earlier release cycle and rejects the -e install of util-genai in the oldest requirements. On a408e14e, adding that line makes scripts/check_deps.py exit 1 with that error. The suggestion below removes it, and with it applied the file merges cleanly and keeps main's typing-extensions==4.7.1 pin. Apply it together with the main merge; the unmerged PR branch still needs the line. I haven't run the oldest env after this change.
|
|
||
| -e util/opentelemetry-util-genai |
There was a problem hiding this comment.
Remove this when merging main; deps-check on main rejects it.
| -e util/opentelemetry-util-genai |
There was a problem hiding this comment.
Thanks for testing this thoroughly!
I have merged upstream/main into this branch, removed -e util/opentelemetry-util-genai from tests/requirements.oldest.txt, and kept the typing-extensions==4.7.1 pin.
…or-content-and-image-parts # Conflicts: # README.md # instrumentation/opentelemetry-instrumentation-genai-portkey/tests/requirements.oldest.txt
|
@lmolkova i believe this PR is ready for review. |
| @@ -0,0 +1 @@ | |||
| Do not consume generator message ``content`` before the SDK call, and capture ``image_url`` parts as image parts. | |||
There was a problem hiding this comment.
The PR now also stops draining messages/tools/tool_calls generators and records audio, file and refusal parts - could the entry mention those?
| Do not consume generator message ``content`` before the SDK call, and capture ``image_url`` parts as image parts. | |
| Do not consume generator ``messages``, ``tools``, ``tool_calls`` or message ``content`` before the SDK call, and capture ``image_url``, ``input_audio``, ``file`` and ``refusal`` content parts. |
The PR description could use the same update - it still describes walking only Sequences and lists test_prepare_input_messages_* tests that are no longer there.
| if hasattr(p.chat.completions, "openai_client"): | ||
| call_args = p.chat.completions.openai_client.with_raw_response.chat.completions.create.call_args | ||
| received_messages = call_args.kwargs["messages"] | ||
| else: | ||
| call_args = p.chat.completions._post.call_args | ||
| body = ( | ||
| call_args.kwargs.get("body") | ||
| if isinstance(call_args.kwargs.get("body"), dict) | ||
| else call_args.kwargs | ||
| ) | ||
| received_messages = body["messages"] |
There was a problem hiding this comment.
nit: this "what did the SDK get" block is repeated in several new tests. A small helper would shorten them:
def _sent_messages(client):
completions = client.chat.completions
if hasattr(completions, "openai_client"):
create = completions.openai_client.with_raw_response.chat.completions.create
return create.call_args.kwargs["messages"]
call_args = completions._post.call_args
body = call_args.kwargs.get("body")
return (body if isinstance(body, dict) else call_args.kwargs)["messages"]| received_messages = body["messages"] | ||
| received_content = received_messages[0]["content"] | ||
| assert not isinstance(received_content, list) | ||
| assert isinstance(received_content, Iterator) |
There was a problem hiding this comment.
A used-up generator is still an Iterator, so this check (and the same one in generator_messages_and_tools_captured and nested_generators_captured) passes on main too, where the content is drained before the SDK call. Could _consume_chat_request keep what it read, so the tests check the SDK got the full content?
_sent: list[Any] = []
def _consume_chat_request(*args: Any, **kwargs: Any) -> None:
...
_sent.append([{**m, "content": list(m["content"])} for m in messages])assert _sent[-1][0]["content"] == [
{"type": "text", "text": "item 0"},
{"type": "text", "text": "item 1"},
]
Description
_prepare_input_messages(portkey/utils.py), iterating anyIterablemessage content consumed generators and one-shot iterators before the wrapped Portkey SDK call executed, leaving the downstream provider request with empty content (same class of issue as [genai-openai] With content capture enabled, generator/iteratormessagesandtoolsare consumed and the request is sent empty #825)._prepare_input_messagespreviously only extractedtype: "text"parts;type: "image_url"items were dropped rather than being recorded asUriPartorBlobPart.To resolve this:
Sequences (excludingstr,bytes, andbytearray) in_prepare_input_messagesso generator content is left unconsumed for the SDK.image_urlparts toUriPart(for remote URLs) andBlobPart(for inline base64data:URLs) viaimage_from_url.SimpleNamespace/SDK models).Fixes #829
Type of change
How has this been tested?
Added unit and integration tests in
instrumentation/opentelemetry-instrumentation-genai-portkey/tests/test_chat_completions.py:test_chat_completions_generator_content_not_consumed: verifies the caller's generator remains unconsumed while span attributes recordparts: [].test_chat_completions_image_parts: verifies remote URLs map toUriPartand inlinedata:URLs decode toBlobPart.test_prepare_input_messages_*: unit tests covering generators, iterators, byte sequence handling, image URLs, and object models.Executed test suite:
pytest instrumentation/opentelemetry-instrumentation-genai-portkey/tests -v(210 passed, 3 skipped)ruff,ruff-formatpassed)Checklist