-
Notifications
You must be signed in to change notification settings - Fork 853
fix(sdk): Updates to support Pydantic v3 #3517
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
cc67f60
165cee0
eb5cdef
d3e92be
bfafda5
30ee5ad
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 |
|---|---|---|
|
|
@@ -64,6 +64,8 @@ class JSONEncoder(json.JSONEncoder): | |
| def default(self, o): | ||
| if dataclasses.is_dataclass(o): | ||
| return dataclasses.asdict(o) | ||
| if hasattr(o, "model_dump_json"): | ||
| return o.model_dump_json() | ||
|
Comment on lines
+67
to
+68
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. Critical: Use Same issue as in the haystack utils file: 🔎 Proposed fix- if hasattr(o, "model_dump_json"):
- return o.model_dump_json()
+ if hasattr(o, "model_dump"):
+ return o.model_dump()🤖 Prompt for AI Agents |
||
| elif hasattr(o, "json"): | ||
| return o.json() | ||
|
Comment on lines
69
to
70
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. Review the The For Pydantic models specifically:
🔎 Suggested approach for backward compatibility- if hasattr(o, "model_dump_json"):
- return o.model_dump_json()
- elif hasattr(o, "json"):
- return o.json()
+ if hasattr(o, "model_dump"):
+ return o.model_dump()
+ elif hasattr(o, "dict"):
+ return o.dict()
elif hasattr(o, "to_json"):
return o.to_json()Note: Keep the
🤖 Prompt for AI Agents |
||
| elif hasattr(o, "to_json"): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,7 +68,7 @@ opentelemetry-instrumentation-groq = { path = "../opentelemetry-instrumentation- | |
| opentelemetry-instrumentation-mcp = { path = "../opentelemetry-instrumentation-mcp", develop = true } | ||
| colorama = "^0.4.6" | ||
| tenacity = ">=8.2.3, <10.0" | ||
| pydantic = ">=1" | ||
| pydantic = ">=2" | ||
|
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. 🧩 Analysis chain🏁 Script executed: # Read the pyproject.toml around the specified lines
sed -n '65,95p' packages/traceloop-sdk/pyproject.toml | cat -nRepository: traceloop/openllmetry Length of output: 1275 Align pydantic dependency constraint with test requirements. The main dependency Additionally, bumping to Pydantic v2 introduces breaking API changes including deprecated parse_raw/parse_file and from_orm methods. Update the main dependency to |
||
| jinja2 = "^3.1.5" | ||
| deprecated = "^1.2.14" | ||
| aiohttp = "^3.11.11" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| from pathlib import Path | ||
| import pytest | ||
| from pydantic import BaseModel | ||
|
|
||
| from traceloop.sdk.decorators import task | ||
| from opentelemetry.semconv_ai import SpanAttributes | ||
|
|
||
|
|
||
| def test_json_encoder_task(exporter, recwarn): | ||
|
|
||
| class TestValue(BaseModel): | ||
| value: int | ||
|
|
||
| @task(name="test_task") | ||
| def test_method(a: TestValue, b: TestValue): | ||
| return TestValue(value=a.value + b.value) | ||
|
|
||
| result = test_method(TestValue(value=2), TestValue(value=3)) | ||
|
|
||
| assert result.value == 5 | ||
|
|
||
| spans = exporter.get_finished_spans() | ||
| assert len(spans) == 1 | ||
| span = spans[0] | ||
| assert span.attributes[SpanAttributes.TRACELOOP_ENTITY_INPUT] == r'{"args": ["{\"value\":2}", "{\"value\":3}"], "kwargs": {}}' | ||
| assert span.attributes[SpanAttributes.TRACELOOP_ENTITY_OUTPUT] == r'"{\"value\":5}"' | ||
|
|
||
| for warning in recwarn: | ||
| file = Path(warning.filename) | ||
| if file.name == "json_encoder.py" and "`json` method is deprecated" in str(warning.message): | ||
| pytest.fail(f"Deprecation warning found: {warning.message}") |
|
Member
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. we have this method in other packages as well - can you update them as well? Also, can you bump pydantic so the test will actually work with the right version? |
Uh oh!
There was an error while loading. Please reload this page.
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.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/opentelemetry-instrumentation-haystack/opentelemetry/instrumentation/haystack/utils.py | head -100Repository: traceloop/openllmetry
Length of output: 3645
Replace
model_dump_json()withmodel_dump().The
model_dump_json()method returns a JSON string, butJSONEncoder.default()expects a Python object (dict, list, etc.). Returning a JSON string causes it to be escaped and encoded again byjson.dumps(), resulting in malformed JSON data in span attributes.Use
model_dump()instead, which returns a dictionary:📝 Committable suggestion
🤖 Prompt for AI Agents
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.
This is actually a good point that potentially applies to the original change in
sdkas well.When we call
model_dump_jsonwe are returned a string representation of that class. If we instead callo.model_dump(mode='json')we are returned a python dictionary where each value is ensured to be a JSON-dumpable type. (See the documentation here).Here is an example of what calling

o.model_dump(mode='json')instead ofo.model_dump_json()looks like in Traceloop:Compare this with the screenshot in the initial PR comment, or here in the span listing for a before/ after of the original implementation vs. calling

o.model_dump(mode='json')IMO
model_dumpis preferableThere 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.
🧩 Analysis chain
🏁 Script executed:
Length of output: 702
@ryanamannion, thank you for the detailed analysis and the visual evidence! You're absolutely right that
model_dump(mode='json')is the better approach here. The screenshots clearly show the improvement—avoiding the double-encoding issue where the JSON string gets escaped again.All three JSON encoder files currently use
model_dump_json()and should be updated consistently to usemodel_dump(mode='json'):The suggested change for each file:
This ensures that:
The
mode='json'parameter is crucial as it handles types like datetime, UUID, etc., converting them to JSON-compatible representations.