-
Notifications
You must be signed in to change notification settings - Fork 216
docs: clarify OpenAI Python parse vs response_format guidance #2884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -381,22 +381,28 @@ | |
|
|
||
| #### Structured Output | ||
|
|
||
| For **structured output parsing**, please use the `response_format` argument to `openai.chat.completions.create()` instead of the Beta API. This will allow you to set Langfuse attributes and metadata. | ||
| For **structured output parsing**, you have two fully instrumented options depending on your `openai` Python SDK version: | ||
|
|
||
| If you rely on parsing Pydantic defintions for your `response_format`, you may leverage the `type_to_response_format_param` utility function from the OpenAI Python SDK to convert the Pydantic definition to a `response_format` dictionary. This is the same function the OpenAI Beta API uses to convert Pydantic definitions to `response_format` dictionaries. | ||
| - **`openai>=1.92.0` (recommended):** use `client.chat.completions.parse(...)`. OpenAI graduated `parse` and `stream` out of beta in [v1.92.0](https://github.com/openai/openai-python/releases/tag/v1.92.0), and Langfuse wraps the stable `openai.resources.chat.completions.Completions.parse` (and the async variant). You can pass a Pydantic model directly via `response_format` and still set Langfuse attributes such as `name`, `metadata`, `langfuse_session_id`, etc. | ||
| - **`openai<1.92.0` (legacy):** the parse helper is only available under `client.beta.chat.completions.parse(...)`. Langfuse also wraps the beta path on these older versions, so attributes like `name` and `metadata` work there too. | ||
|
Check warning on line 387 in content/integrations/model-providers/openai-py.mdx
|
||
|
|
||
|
Check warning on line 388 in content/integrations/model-providers/openai-py.mdx
|
||
|
Comment on lines
+384
to
+388
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The parent OpenAI Beta APIs section intro (just above line 381) still says "we fully support only the stable APIs in the OpenAI SDK. If you are using a beta API, you can still use the Langfuse SDK by wrapping the OpenAI SDK manually with the @observe() decorator", but the new Structured Output subsection placed under it now explicitly states that Langfuse instruments the legacy beta parse path too. Readers following the parent intro would unnecessarily wrap with Extended reasoning...What the bug is. This PR rewrites the Structured Output subsection of
The new subsection directly contradicts that intro by stating:
Why it's a problem. A reader who scans the page top-down hits the section header ("OpenAI Beta APIs"), reads the intro that says beta APIs need manual Section structure is also stale. Since Step-by-step proof.
How to fix. Either (a) update the intro paragraph under |
||
| <Callout type="info"> | ||
| On `openai>=1.92.0`, calls to `client.beta.chat.completions.parse(...)` are | ||
| re-routed to the stable `parse` method by the OpenAI SDK, so they remain | ||
| instrumented. Prefer `client.chat.completions.parse(...)` in new code. | ||
| </Callout> | ||
|
|
||
| ```python | ||
| from langfuse import get_client | ||
| from langfuse.openai import openai | ||
| from openai.lib._parsing._completions import type_to_response_format_param | ||
| from pydantic import BaseModel | ||
|
|
||
| class CalendarEvent(BaseModel): | ||
| name: str | ||
| date: str | ||
| participants: list[str] | ||
| name: str | ||
| date: str | ||
| participants: list[str] | ||
|
|
||
| completion = openai.chat.completions.create( | ||
| completion = openai.chat.completions.parse( | ||
| model="gpt-4o-2024-08-06", | ||
| messages=[ | ||
| {"role": "system", "content": "Extract the event information."}, | ||
|
|
@@ -405,15 +411,42 @@ | |
| "content": "Alice and Bob are going to a science fair on Friday.", | ||
| }, | ||
| ], | ||
| response_format=type_to_response_format_param(CalendarEvent), | ||
| response_format=CalendarEvent, | ||
| name="extract-calendar-event", | ||
| metadata={"langfuse_tags": ["structured-output"]}, | ||
| ) | ||
|
|
||
| print(completion) | ||
| print(completion.choices[0].message.parsed) | ||
|
|
||
| # Flush via global client | ||
| get_client().flush() | ||
| ``` | ||
|
|
||
| If you cannot upgrade the OpenAI SDK and want to stay on `chat.completions.create`, you can still pass a Pydantic model by converting it with `type_to_response_format_param` from the OpenAI SDK: | ||
|
|
||
| ```python | ||
| from langfuse.openai import openai | ||
| from openai.lib._parsing._completions import type_to_response_format_param | ||
| from pydantic import BaseModel | ||
|
|
||
| class CalendarEvent(BaseModel): | ||
| name: str | ||
| date: str | ||
| participants: list[str] | ||
|
|
||
| completion = openai.chat.completions.create( | ||
| model="gpt-4o-2024-08-06", | ||
| messages=[ | ||
| {"role": "system", "content": "Extract the event information."}, | ||
| { | ||
| "role": "user", | ||
| "content": "Alice and Bob are going to a science fair on Friday.", | ||
| }, | ||
| ], | ||
| response_format=type_to_response_format_param(CalendarEvent), | ||
| ) | ||
| ``` | ||
|
|
||
| #### Assistants API | ||
|
|
||
| Tracing of the assistants api is not supported by this integration as OpenAI Assistants have server-side state that cannot easily be captured without additional api requests. Check out this [notebook](/integrations/model-providers/openai-assistants-api) for an end-to-end example on how to best track usage of the assistants api in Langfuse. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 The new note groups
name,metadata, andlangfuse_session_idtogether as 'Langfuse attributes' (line 386 and again in the cookbook .md/.ipynb), implying all three are direct kwargs ofchat.completions.parse(...). However, the same MDX file's 'Custom trace properties' table (lines 206-211) lists onlyname/metadata/trace_id/parent_observation_idas direct kwargs, and the 'Setting trace attributes' section (line 230) shows thatlangfuse_session_id,langfuse_user_id, andlangfuse_tagsmust live inside themetadatadict. A reader could tryparse(..., langfuse_session_id='x')and have it silently dropped. Suggest rewording to something like 'attributes such asnameandmetadata(withlangfuse_session_id,langfuse_user_id,langfuse_tagsetc. nested insidemetadata)' — note the existing example at line 416 already demonstrates the correct pattern withmetadata={"langfuse_tags": [...]}.Extended reasoning...
The bug. The new prose at
content/integrations/model-providers/openai-py.mdxlines 386-387 (and the same wording duplicated incontent/guides/cookbook/integration_openai_structured_output.mdat lines 58 and 193, plus the matching.ipynbcells) reads:This sentence flattens two different things into one comma-separated list:
nameandmetadataare real keyword arguments accepted by the Langfuse-wrapped OpenAI call, butlangfuse_session_idis not — it is a key inside themetadatadict.Why this contradicts the rest of the file. The same MDX has a 'Custom trace properties' table at lines 204-211 that lists exactly four direct kwargs:
name,metadata,trace_id,parent_observation_id.langfuse_session_idis deliberately absent from that table. The 'Setting trace attributes (session_id,user_id,tags)' section that follows (lines 213-236) makes the correct usage explicit:So the new prose contradicts the canonical documentation just 150 lines above it.
The new prose also contradicts its own example. The very next code block (line 416 in the MDX) does the right thing:
Here
langfuse_tagsis correctly nested insidemetadata, not passed as a direct kwarg. The example demonstrates the correct pattern; the prose above it does not.Step-by-step proof of how a reader gets misled.
Reader lands on the new 'Structured Output' section because they want session/user attribution on a
parsecall.They read the bullet: "You can pass a Pydantic model directly via
response_formatand still set Langfuse attributes such asname,metadata,langfuse_session_id, etc."By analogy with
name="..."(a direct kwarg shown in the example), they write:The wrapper recognizes
nameand consumes it.langfuse_session_idis not a recognized wrapper kwarg, so it is either passed through to the underlying OpenAI client (which will reject it as an unknown parameter, or silently ignore depending on version), or simply dropped — in any case, it never makes it onto the trace as a session id.The reader's traces have no session id and they don't know why; the docs that would tell them (lines 213-236) are now contradicted by the new bullet they trusted.
Cross-file impact. The cookbook
.mdat lines 58 and 193 has the same sentence, but in that file the disambiguating 'Custom trace properties' / 'Setting trace attributes' sections do not exist nearby — so a cookbook reader has even less chance of catching the issue. The.ipynbcarries the same wording.Fix. Reword along the lines of:
This keeps the reassurance that the stable
parsepath is fully instrumented, while no longer presenting metadata-keys as if they were direct kwargs. The change is text-only and applies to: the new note in the MDX (around line 386), the same note in the cookbook.md(line 58 and 193), and the corresponding cells in the.ipynb.