Skip to content

Commit

Permalink
feat(openai): add client tag, differentiate llmobs span name (#10661)
Browse files Browse the repository at this point in the history
This PR does 2 (related) things:
1. Add a new tag `openai.request.client` on all OpenAI integration spans
which differentiates whether or not the request was sent to OpenAI or
Azure OpenAI. (possible values: `OpenAI/AzureOpenAI`)
2. Change LLMObs OpenAI span names to be prefixed with the client name.
Here, the client name includes a `Async` prefix if necessary
(`AsyncOpenAI/AsyncAzureOpenAI`), or otherwise the same as the
`openai.request.client` tag. The span names keep the resource name
component.

## Checklist
- [x] PR author has checked that all the criteria below are met
- The PR description includes an overview of the change
- The PR description articulates the motivation for the change
- The change includes tests OR the PR description describes a testing
strategy
- The PR description notes risks associated with the change, if any
- Newly-added code is easy to change
- The change follows the [library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
- The change includes or references documentation updates if necessary
- Backport labels are set (if
[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))

## Reviewer Checklist
- [x] Reviewer has checked that all the criteria below are met 
- Title is accurate
- All changes are related to the pull request's stated goal
- Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes
- Testing strategy adequately addresses listed risks
- Newly-added code is easy to change
- Release note makes sense to a user of the library
- If necessary, author has acknowledged and discussed the performance
implications of this PR as reported in the benchmarks PR comment
- 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)
  • Loading branch information
Yun-Kim authored Sep 16, 2024
1 parent c7de1d5 commit 2e9d3b1
Show file tree
Hide file tree
Showing 68 changed files with 205 additions and 68 deletions.
9 changes: 9 additions & 0 deletions ddtrace/llmobs/_integrations/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ def _set_base_span_tags(self, span: Span, **kwargs) -> None:
span.set_tag_str("openai.organization.id", v or "")
else:
span.set_tag_str("openai.%s" % attr, str(v))
span.set_tag_str("openai.request.client", "AzureOpenAI" if self._is_azure_openai(span) else "OpenAI")

@staticmethod
def _is_azure_openai(span):
"""Check if the traced operation is an AzureOpenAI operation using the request's base URL."""
base_url = span.get_tag("openai.base_url") or span.get_tag("openai.api_base")
if not base_url or not isinstance(base_url, str):
return False
return "azure" in base_url.lower()

@classmethod
def _logs_tags(cls, span: Span) -> str:
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/llmobs/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ def _get_span_name(span: Span) -> str:
if span.name in (LANGCHAIN_APM_SPAN_NAME, GEMINI_APM_SPAN_NAME) and span.resource != "":
return span.resource
elif span.name == OPENAI_APM_SPAN_NAME and span.resource != "":
return "openai.{}".format(span.resource)
client_name = span.get_tag("openai.request.client") or "OpenAI"
return "{}.{}".format(client_name, span.resource)
return span.name


Expand Down
9 changes: 9 additions & 0 deletions releasenotes/notes/feat-openai-azure-23cf45cfa854a5e5.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
openai: The OpenAI integration now includes a new ``openai.request.client`` tag with the possible values ``OpenAI/AzureOpenAI``
to help differentiate whether the request was made to Azure OpenAI or OpenAI.
- |
LLM Observability: LLM Observability spans generated by the OpenAI integration now have updated span names.
Span names are now prefixed with the OpenAI client name (possible values: ``OpenAI/AzureOpenAI``)
instead of the default ``openai`` prefix to better differentiate whether the request was made to Azure OpenAI or OpenAI.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ def test_completion(self, genai, ddtrace_global_config, mock_llmobs_writer, mock
metadata={"temperature": 1.0, "max_output_tokens": 35},
token_metrics={"input_tokens": 12, "output_tokens": 30, "total_tokens": 42},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand All @@ -69,7 +68,6 @@ async def test_completion_async(
metadata={"temperature": 1.0, "max_output_tokens": 35},
token_metrics={"input_tokens": 12, "output_tokens": 30, "total_tokens": 42},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -98,7 +96,6 @@ def test_completion_error(self, genai, ddtrace_global_config, mock_llmobs_writer
error_stack=span.get_tag("error.stack"),
metadata={"temperature": 1.0, "max_output_tokens": 35},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
)

Expand Down Expand Up @@ -131,7 +128,6 @@ async def test_completion_error_async(
error_stack=span.get_tag("error.stack"),
metadata={"temperature": 1.0, "max_output_tokens": 35},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
)

Expand Down Expand Up @@ -165,7 +161,6 @@ def test_completion_multiple_messages(
metadata={"temperature": 1.0, "max_output_tokens": 35},
token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -199,7 +194,6 @@ async def test_completion_multiple_messages_async(
metadata={"temperature": 1.0, "max_output_tokens": 35},
token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -233,7 +227,6 @@ def test_chat_completion(self, genai, ddtrace_global_config, mock_llmobs_writer,
metadata={"temperature": 1.0, "max_output_tokens": 35},
token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -269,7 +262,6 @@ async def test_chat_completion_async(
metadata={"temperature": 1.0, "max_output_tokens": 35},
token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -305,7 +297,6 @@ def test_completion_system_prompt(self, genai, ddtrace_global_config, mock_llmob
metadata={"temperature": 1.0, "max_output_tokens": 50},
token_metrics={"input_tokens": 29, "output_tokens": 45, "total_tokens": 74},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -343,7 +334,6 @@ async def test_completion_system_prompt_async(
metadata={"temperature": 1.0, "max_output_tokens": 50},
token_metrics={"input_tokens": 29, "output_tokens": 45, "total_tokens": 74},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -372,7 +362,6 @@ def test_completion_stream(self, genai, ddtrace_global_config, mock_llmobs_write
metadata={"temperature": 1.0, "max_output_tokens": 60},
token_metrics={"input_tokens": 6, "output_tokens": 52, "total_tokens": 58},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -403,7 +392,6 @@ async def test_completion_stream_async(
metadata={"temperature": 1.0, "max_output_tokens": 60},
token_metrics={"input_tokens": 6, "output_tokens": 52, "total_tokens": 58},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -438,7 +426,6 @@ def test_completion_tool_call(self, genai, ddtrace_global_config, mock_llmobs_wr
metadata={"temperature": 1.0, "max_output_tokens": 30},
token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -475,7 +462,6 @@ async def test_completion_tool_call_async(
metadata={"temperature": 1.0, "max_output_tokens": 30},
token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -517,7 +503,6 @@ def test_gemini_completion_tool_stream(
metadata={"temperature": 1.0, "max_output_tokens": 30},
token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -559,7 +544,6 @@ async def test_gemini_completion_tool_stream_async(
metadata={"temperature": 1.0, "max_output_tokens": 30},
token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand All @@ -586,7 +570,6 @@ def test_gemini_completion_image(self, genai, ddtrace_global_config, mock_llmobs
metadata={"temperature": 1.0, "max_output_tokens": 30},
token_metrics={"input_tokens": 277, "output_tokens": 14, "total_tokens": 291},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)

Expand Down Expand Up @@ -615,6 +598,5 @@ async def test_gemini_completion_image_async(
metadata={"temperature": 1.0, "max_output_tokens": 30},
token_metrics={"input_tokens": 277, "output_tokens": 14, "total_tokens": 291},
tags={"ml_app": "<ml-app-name>"},
integration="gemini",
)
mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event)
10 changes: 0 additions & 10 deletions tests/contrib/langchain/test_langchain_llmobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role=N
metadata=metadata,
token_metrics={},
tags={"ml_app": "langchain_test"},
integration="langchain",
)
)

Expand All @@ -81,7 +80,6 @@ def _assert_expected_llmobs_chain_span(span, mock_llmobs_span_writer, input_valu
input_value=input_value if input_value is not None else mock.ANY,
output_value=output_value if output_value is not None else mock.ANY,
tags={"ml_app": "langchain_test"},
integration="langchain",
)
mock_llmobs_span_writer.enqueue.assert_any_call(expected_chain_span_event)

Expand Down Expand Up @@ -388,7 +386,6 @@ def test_llmobs_embedding_query(self, langchain, mock_llmobs_span_writer, mock_t
input_documents=[{"text": "hello world"}],
output_value="[1 embedding(s) returned with size 1536]",
tags={"ml_app": "langchain_test"},
integration="langchain",
)
)

Expand All @@ -414,7 +411,6 @@ def test_llmobs_embedding_documents(self, langchain, mock_llmobs_span_writer, mo
input_documents=[{"text": "hello world"}, {"text": "goodbye world"}],
output_value="[2 embedding(s) returned with size 1536]",
tags={"ml_app": "langchain_test"},
integration="langchain",
)
)

Expand Down Expand Up @@ -442,7 +438,6 @@ def test_llmobs_similarity_search(self, langchain, mock_llmobs_span_writer, mock
output_documents=[{"text": mock.ANY, "id": mock.ANY, "name": mock.ANY}],
output_value="[1 document(s) retrieved]",
tags={"ml_app": "langchain_test"},
integration="langchain",
)
mock_llmobs_span_writer.enqueue.assert_any_call(expected_span)
assert mock_llmobs_span_writer.enqueue.call_count == 2
Expand Down Expand Up @@ -653,7 +648,6 @@ def test_llmobs_embedding_query(self, langchain_community, langchain_openai, moc
input_documents=[{"text": "hello world"}],
output_value="[1 embedding(s) returned with size 1536]",
tags={"ml_app": "langchain_test"},
integration="langchain",
)
)

Expand All @@ -680,7 +674,6 @@ def test_llmobs_embedding_documents(
input_documents=[{"text": "hello world"}, {"text": "goodbye world"}],
output_value="[2 embedding(s) returned with size 1536]",
tags={"ml_app": "langchain_test"},
integration="langchain",
)
)

Expand Down Expand Up @@ -711,7 +704,6 @@ def test_llmobs_similarity_search(self, langchain_openai, langchain_pinecone, mo
],
output_value="[1 document(s) retrieved]",
tags={"ml_app": "langchain_test"},
integration="langchain",
)
mock_llmobs_span_writer.enqueue.assert_any_call(expected_span)

Expand Down Expand Up @@ -759,7 +751,6 @@ def add(a: int, b: int) -> int:
metadata={"temperature": 0.7},
token_metrics={},
tags={"ml_app": "langchain_test"},
integration="langchain",
)
)

Expand Down Expand Up @@ -803,7 +794,6 @@ def circumference_tool(radius: float) -> float:
},
},
tags={"ml_app": "langchain_test"},
integration="langchain",
)
)

Expand Down
Loading

0 comments on commit 2e9d3b1

Please sign in to comment.