Skip to content

Commit e4d4599

Browse files
github-actions[bot]Yun-KimZStriker19
authored
fix(openai): safely tag potentially null chat completion message [backport 1.19] (#7080)
Backport a25570b from #7054 to 1.19. This fix ensures that we default to tagging empty strings from OpenAI's ChatCompletion response message content field. With the recent updates to OpenAI's API on allowing function calling arguments to the ChatCompletion endpoint, the ChatCompletion response can now (if function calling is specified in the request) contain an additional `function_call` parameter which contains the LLM text response, and the original `content` field now is `None`. Previously, this `content` field was guaranteed to contain a text value from the LLM, but now when we try to directly tag `None` onto the span, this results in `TypeError`. With this fix, we will check the value of the message content field and default to an empty string before tagging. ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that. Co-authored-by: Yun Kim <[email protected]> Co-authored-by: Zachary Groves <[email protected]>
1 parent 5871f0d commit e4d4599

File tree

5 files changed

+193
-4
lines changed

5 files changed

+193
-4
lines changed

ddtrace/contrib/openai/_endpoint_hooks.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,8 @@ def _record_response(self, pin, integration, span, args, kwargs, resp, error):
279279
idx = choice["index"]
280280
span.set_tag_str("openai.response.choices.%d.finish_reason" % idx, choice.get("finish_reason"))
281281
if integration.is_pc_sampled_span(span) and choice.get("message"):
282-
span.set_tag_str(
283-
"openai.response.choices.%d.message.content" % idx,
284-
integration.trunc(choice.get("message", {}).get("content", "")),
285-
)
282+
content = choice.get("message", {}).get("content", "") or ""
283+
span.set_tag_str("openai.response.choices.%d.message.content" % idx, integration.trunc(content))
286284
span.set_tag_str(
287285
"openai.response.choices.%d.message.role" % idx,
288286
integration.trunc(choice.get("message", {}).get("role", "")),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
openai: This fix resolves an issue where chat completion requests with function calls led to failing to
5+
tag null message content fields in the chat completion response.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
interactions:
2+
- request:
3+
body: '{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "\n David
4+
Nguyen is a sophomore majoring in computer science at Stanford University and
5+
has a GPA of 3.8.\n David is an active member of the university''s Chess
6+
Club and the South Asian Student Association.\n He hopes to pursue a career
7+
in software engineering after graduating.\n "}], "functions": [{"name": "extract_student_info",
8+
"description": "Get the student information from the body of the input text",
9+
"parameters": {"type": "object", "properties": {"name": {"type": "string", "description":
10+
"Name of the person"}, "major": {"type": "string", "description": "Major subject."},
11+
"school": {"type": "string", "description": "The university name."}, "grades":
12+
{"type": "integer", "description": "GPA of the student."}, "clubs": {"type":
13+
"array", "description": "School clubs for extracurricular activities. ", "items":
14+
{"type": "string", "description": "Name of School Club"}}}}}], "function_call":
15+
"auto", "user": "ddtrace-test"}'
16+
headers:
17+
Accept:
18+
- '*/*'
19+
Accept-Encoding:
20+
- gzip, deflate
21+
Connection:
22+
- keep-alive
23+
Content-Length:
24+
- '1014'
25+
Content-Type:
26+
- application/json
27+
User-Agent:
28+
- OpenAI/v1 PythonBindings/0.27.2
29+
X-OpenAI-Client-User-Agent:
30+
- '{"bindings_version": "0.27.2", "httplib": "requests", "lang": "python", "lang_version":
31+
"3.10.5", "platform": "macOS-13.6-arm64-arm-64bit", "publisher": "openai",
32+
"uname": "Darwin 22.6.0 Darwin Kernel Version 22.6.0: Fri Sep 15 13:41:28
33+
PDT 2023; root:xnu-8796.141.3.700.8~1/RELEASE_ARM64_T6000 arm64"}'
34+
method: POST
35+
uri: https://api.openai.com/v1/chat/completions
36+
response:
37+
body:
38+
string: !!binary |
39+
H4sIAAAAAAAAA1RSS2/iMBC+8ytGcwZESoE2t5bdK3tASCs1FTLOQFyccWSPu60Q/33lBEK5WNb3
40+
8jx8GgCgKTEH1JUSXTd29DSdVdXqWL0+/3n9V69XR7/8y9ptYhl+b3CYHG73QVqurrF2dWNJjOOO
41+
1p6UUErN5s+zxeNiPslaonYl2WQ7NDKajmcjiX7nRpN5Nr04K2c0BczhbQAAcGrPVCOX9IU5TIZX
42+
pKYQ1IEw70UA6J1NCKoQTBDFgsMbqR0LcSqbo7U/iH1knarfamXtXSAAsqrbSPoSr7Rsg8SSWLaG
43+
9+5HOgAqf4g1saTy8VQwQNG6C8yhwF/q05SwOsRv4gKHHV2rD+c7Pg0xCnkI2hBr6jVBV87ZTrQW
44+
xXvnS9iw+SQfjHz3uoNXJYWkm46fLpi2cddCbwUuKwoBljbuChymLBelgpdgFMO6awpeQnDaqDSM
45+
At8LPmPf4PlyO/cb2Bs2odp6UsFx6vl+joOr671dbbzbFjbe1Y1sxR2J08Cy2aILxttvurE9KU6U
46+
veEP2eMgPXIe/AcAAP//AwB/irZaygIAAA==
47+
headers:
48+
CF-Cache-Status:
49+
- DYNAMIC
50+
CF-RAY:
51+
- 80cd0f8a0c8241f5-EWR
52+
Cache-Control:
53+
- no-cache, must-revalidate
54+
Connection:
55+
- keep-alive
56+
Content-Encoding:
57+
- gzip
58+
Content-Type:
59+
- application/json
60+
Date:
61+
- Tue, 26 Sep 2023 17:00:01 GMT
62+
Server:
63+
- cloudflare
64+
Transfer-Encoding:
65+
- chunked
66+
access-control-allow-origin:
67+
- '*'
68+
alt-svc:
69+
- h3=":443"; ma=86400
70+
openai-model:
71+
- gpt-3.5-turbo-0613
72+
openai-organization:
73+
- datadog-4
74+
openai-processing-ms:
75+
- '692'
76+
openai-version:
77+
- '2020-10-01'
78+
strict-transport-security:
79+
- max-age=15724800; includeSubDomains
80+
x-ratelimit-limit-requests:
81+
- '10000'
82+
x-ratelimit-limit-tokens:
83+
- '1000000'
84+
x-ratelimit-remaining-requests:
85+
- '9999'
86+
x-ratelimit-remaining-tokens:
87+
- '999910'
88+
x-ratelimit-reset-requests:
89+
- 6ms
90+
x-ratelimit-reset-tokens:
91+
- 5ms
92+
x-request-id:
93+
- 3b5828ba018e216550b170f2c77f88f3
94+
status:
95+
code: 200
96+
message: OK
97+
version: 1

tests/contrib/openai/test_openai.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,46 @@ def test_chat_completion(api_key_in_env, request_api_key, openai, openai_vcr, sn
572572
)
573573

574574

575+
@pytest.mark.snapshot(ignores=["meta.http.useragent"])
576+
def test_chat_completion_function_calling(openai, openai_vcr, snapshot_tracer):
577+
if not hasattr(openai, "ChatCompletion"):
578+
pytest.skip("ChatCompletion not supported for this version of openai")
579+
student_description = """
580+
David Nguyen is a sophomore majoring in computer science at Stanford University and has a GPA of 3.8.
581+
David is an active member of the university's Chess Club and the South Asian Student Association.
582+
He hopes to pursue a career in software engineering after graduating.
583+
"""
584+
student_custom_functions = [
585+
{
586+
"name": "extract_student_info",
587+
"description": "Get the student information from the body of the input text",
588+
"parameters": {
589+
"type": "object",
590+
"properties": {
591+
"name": {"type": "string", "description": "Name of the person"},
592+
"major": {"type": "string", "description": "Major subject."},
593+
"school": {"type": "string", "description": "The university name."},
594+
"grades": {"type": "integer", "description": "GPA of the student."},
595+
"clubs": {
596+
"type": "array",
597+
"description": "School clubs for extracurricular activities. ",
598+
"items": {"type": "string", "description": "Name of School Club"},
599+
},
600+
},
601+
},
602+
},
603+
]
604+
605+
with openai_vcr.use_cassette("chat_completion_function_call.yaml"):
606+
openai.ChatCompletion.create(
607+
model="gpt-3.5-turbo",
608+
messages=[{"role": "user", "content": student_description}],
609+
functions=student_custom_functions,
610+
function_call="auto",
611+
user="ddtrace-test",
612+
)
613+
614+
575615
@pytest.mark.parametrize("ddtrace_config_openai", [dict(metrics_enabled=b) for b in [True, False]])
576616
def test_enable_metrics(openai, openai_vcr, ddtrace_config_openai, mock_metrics, mock_tracer):
577617
"""Ensure the metrics_enabled configuration works."""
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[[
2+
{
3+
"name": "openai.request",
4+
"service": null,
5+
"resource": "createChatCompletion",
6+
"trace_id": 0,
7+
"span_id": 1,
8+
"parent_id": 0,
9+
"meta": {
10+
"_dd.p.dm": "-0",
11+
"component": "openai",
12+
"language": "python",
13+
"openai.api_base": "https://api.openai.com/v1",
14+
"openai.api_type": "open_ai",
15+
"openai.organization.name": "datadog-4",
16+
"openai.request.endpoint": "/v1/chat/completions",
17+
"openai.request.messages.0.content": "\\n David Nguyen is a sophomore majoring in computer science at Stanford University and has a GPA of 3.8.\\n David is an act...",
18+
"openai.request.messages.0.name": "",
19+
"openai.request.messages.0.role": "user",
20+
"openai.request.method": "POST",
21+
"openai.request.model": "gpt-3.5-turbo",
22+
"openai.request.user": "ddtrace-test",
23+
"openai.response.choices.0.finish_reason": "function_call",
24+
"openai.response.choices.0.message.content": "",
25+
"openai.response.choices.0.message.name": "",
26+
"openai.response.choices.0.message.role": "assistant",
27+
"openai.response.id": "chatcmpl-835hhNkhB9OBwmSNkrCXncoUudsEU",
28+
"openai.response.model": "gpt-3.5-turbo-0613",
29+
"openai.user.api_key": "sk-...key>",
30+
"runtime-id": "2fab29c9652a4617a2afcf0ff42a3fa9"
31+
},
32+
"metrics": {
33+
"_dd.measured": 1,
34+
"_dd.top_level": 1,
35+
"_dd.tracer_kr": 1.0,
36+
"_sample_rate": 1.0,
37+
"_sampling_priority_v1": 1,
38+
"openai.organization.ratelimit.requests.remaining": 9999,
39+
"openai.organization.ratelimit.tokens.remaining": 999910,
40+
"openai.response.choices_count": 1,
41+
"openai.response.created": 1695747601,
42+
"openai.response.usage.completion_tokens": 57,
43+
"openai.response.usage.prompt_tokens": 157,
44+
"openai.response.usage.total_tokens": 214,
45+
"process_id": 26590
46+
},
47+
"duration": 1024064000,
48+
"start": 1695747600754577000
49+
}]]

0 commit comments

Comments
 (0)