Skip to content
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

[WIP] Add Initial Support for Instrumenting OpenAI Python Library - Chat Completion Create #2759

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

karthikscale3
Copy link

@karthikscale3 karthikscale3 commented Jul 31, 2024

Description

This PR adds support for tracing the official python openai library.

This pull request introduces initial support for instrumenting the OpenAI Python library, specifically targeting the chat.completion.create method. This implementation aligns with the GenAI semantic conventions.

Fixes #1796

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

This is a work in progress PR at the moment and I plan to add unit tests and update this space.

Does This PR Require a Core Repo Change?

  • Yes. - Link to PR:
  • No.

Checklist:

See contributing.md for styleguide, changelog guidelines, and more.

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

@karthikscale3 karthikscale3 marked this pull request as draft July 31, 2024 07:15
Copy link
Contributor

@xrmx xrmx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a first round of comments, thanks!

build-backend = "hatchling.build"

[project]
name = "opentelemetry-instrumentation-openai"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this is already taken and we have to sort out how to handle that

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be really tricky, do we have flexibility regarding naming or should we be following a certain convention for naming?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convention for naming would suggest this name unfortunately 😓

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest opentelemetry-instrumentation-openai-v2 and the following plan:

  • we'll start with this name and evolve it along with semconv to have feature parity with opentelemetry-instrumentation-openai
  • we'll need to check with @nirga, but based on the previous discussions they should, at some point, be able to publish opentelemetry-instrumentation-openai package from OTel
  • when Traceloop and we are ready, we'll start publishing opentelemetry-instrumentation-openai v2 from OTel and stop publishing opentelemetry-instrumentation-openai-v2.

This would let us unblock this work and will give traceloop time to migrate. All the details need further discussion (and of course name I came up with is almost random - any other suggestions are welcome).

from pydantic import BaseModel, ConfigDict, Field


class SpanAttributes:
Copy link
Contributor

@xrmx xrmx Jul 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use the attributes from the semantic conventions package instead of duplicating them here, see https://github.com/open-telemetry/opentelemetry-python/blob/main/opentelemetry-semantic-conventions/src/opentelemetry/semconv/_incubating/attributes/gen_ai_attributes.py

It's fine to add here the missing ones

@@ -0,0 +1,8 @@
openai==1.37.1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add all the openai dependencies to keep things reproducible

RESPONSE = "response"


class LLMSpanAttributes(BaseModel):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate a bit on why we need pydantic for this?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's mainly an extra layer of validation, so on patch level what happens is the following

  1. prepare an object containing span attributes needed
  2. validate that required fields are present for ex gen_ai.operation.name & gen_ai.request.model
  3. save those attributes upon successful validation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we validate it with tests? If something fails in runtime our validation would not really help anyone.

Copy link

linux-foundation-easycla bot commented Aug 7, 2024

CLA Signed

The committers listed above are authorized under a signed CLA.

):
set_span_attribute(
span,
SpanAttributes.LLM_SYSTEM_FINGERPRINT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not in semconv, could you please send the PR to add it there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


def set_span_attributes(span: Span, attributes: dict):
for field, value in attributes.model_dump(by_alias=True).items():
set_span_attribute(span, field, value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which attributes it will produce? what would be their names?

we should record attributes documented in the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/gen-ai/gen-ai-spans.md#genai-attributes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

span.add_event(
name=SpanAttributes.LLM_CONTENT_PROMPT,
attributes={
SpanAttributes.LLM_PROMPTS: prompt,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be an event

if usage is not None:
set_span_attribute(
span,
SpanAttributes.LLM_USAGE_PROMPT_TOKENS,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use https://github.com/open-telemetry/opentelemetry-python/blob/main/opentelemetry-semantic-conventions/src/opentelemetry/semconv/_incubating/attributes/gen_ai_attributes.py -

SpanAttributes.LLM_USAGE_PROMPT_TOKENS is deprecated both in the spec and in the semconv package.

We might need to update to the latest semconv package version to use all new attributes.

Copy link
Contributor

@lmolkova lmolkova left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start, thanks a lot for doing this!

lmolkova added a commit to open-telemetry/opentelemetry-specification that referenced this pull request Aug 28, 2024
…ween external and otel instrumentations (#4187)

Some package managers (PyPi) don't provide means to reserve namespaces
for projects. We also have a number of **external** instrumentation
libraries in python that follow current guidance and use
`opentelemetry-instrumentation-{component}` naming pattern.

These libraries are hard to distinguish from otel-authored ones. Also,
when someone (legitimately following existing guidance) creates an
external instrumentation package like this, it blocks our ability to
have OTel-authored instrumentation with this 'good' name.

See
open-telemetry/opentelemetry-python-contrib#2759 (comment)
for real-life example.

## Changes

This PR changes the recommendation to:
- otel authored instrumentation should use
`opentelemetry-instrumentation-*` pattern
- external instrumentation should not use this pattern and should prefix
lib name with their company/project/etc name

* ~~[ ] Related issues #~~
* ~~[ ] Related [OTEP(s)](https://github.com/open-telemetry/oteps) #~~
* ~~[ ] Links to the prototypes (when adding or changing features)~~
* [x]
[`CHANGELOG.md`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/CHANGELOG.md)
file updated for non-trivial changes
* ~~[ ]
[`spec-compliance-matrix.md`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix.md)
updated if necessary~~

---------

Co-authored-by: Armin Ruech <[email protected]>
@lzchen lzchen added the gen-ai Related to generative AI label Aug 28, 2024
def _instrument(self, **kwargs):
"""Enable OpenAI instrumentation."""
tracer_provider = kwargs.get("tracer_provider")
tracer = get_tracer(__name__, "", tracer_provider)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
tracer = get_tracer(__name__, "", tracer_provider)
tracer = get_tracer(__name__, "", tracer_provider, schema_url=Schemas.V1_27_0)

span = tracer.start_span(
name=span_name,
kind=SpanKind.CLIENT,
context=set_span_in_context(trace.get_current_span()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be no need, this is the default behavior

Suggested change
context=set_span_in_context(trace.get_current_span()),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, will remove


except Exception as error:
span.set_status(Status(StatusCode.ERROR, str(error)))
span.set_attribute("error.type", error.__class__.__name__)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from opentelemetry.semconv.attributes import error_attributes
Suggested change
span.set_attribute("error.type", error.__class__.__name__)
span.set_attribute(error_attributes.ERROR_TYPE, type(error).__qualname__)

error.type should be fully qualified

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree will fix

set_span_attribute(
span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, result.model
)
print(result)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops

if choice.finish_reason:
set_span_attribute(
span,
GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be an array of all reasons returned

  finish_reasons = []

  for choice in choices:
      finish_reasons.append(choice.finish_reason or "error")
  
  set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS, finish_reasons)

Comment on lines 160 to 161
or kwargs.get("k")
or kwargs.get("top_k")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openai does not have top_k or k parameters, do we need it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, will be removed

else None
)
top_k = (
kwargs.get("n")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n represents number of choices, while Claude has top_k which is " Only sample from the top K options for each subsequent token." - they don't seem to be related.

GenAIAttributes.GEN_AI_SYSTEM: GenAIAttributes.GenAiSystemValues.OPENAI.value,
GenAIAttributes.GEN_AI_REQUEST_MODEL: model or kwargs.get("model"),
GenAIAttributes.GEN_AI_REQUEST_TEMPERATURE: kwargs.get("temperature"),
GenAIAttributes.GEN_AI_REQUEST_TOP_K: top_k,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my previous comments about top_k

Suggested change
GenAIAttributes.GEN_AI_REQUEST_TOP_K: top_k,

# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = "0.48b0.dev"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be

Suggested change
__version__ = "0.48b0.dev"
__version__ = "0.49b0.dev"

now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we really need some tests :) happy to help with additional test cases if you can create the first happy case test with the tool you mentioned that uses recorded content.

__name__,
"",
tracer_provider,
schema_url="https://opentelemetry.io/schemas/1.27.0",
Copy link
Contributor

@lmolkova lmolkova Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not

from opentelemetry.semconv.schemas import Schemas

...

schema_url=Schemas.V1_27_0

?

if exc_type is not None:
self.span.set_status(Status(StatusCode.ERROR, str(exc_val)))
self.span.set_attribute(
ErrorAttributes.ERROR_TYPE, exc_type.__name__
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ErrorAttributes.ERROR_TYPE, exc_type.__name__
ErrorAttributes.ERROR_TYPE, exc_type.__qualname__

def __exit__(self, exc_type, exc_val, exc_tb):
try:
if exc_type is not None:
self.span.set_status(Status(StatusCode.ERROR, str(exc_val)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider moving error status reporting to a helper method

RESPONSE = "response"


class LLMSpanAttributes(BaseModel):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we validate it with tests? If something fails in runtime our validation would not really help anyone.

)
gen_ai_usage_total_tokens: Optional[float] = Field(
None,
alias="gen_ai.usage.total_tokens",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove unused attributes

@truptiparkar7
Copy link

@karthikscale3 This is great! thanks for starting this! We are interested in using this as well. How can we prioritize it to be approved soon? Also are there any documentation/ readme with same example & steps on how users can use this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gen-ai Related to generative AI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Contributing an OpenAI instrumentor.
6 participants