-
Notifications
You must be signed in to change notification settings - Fork 509
OpenAI Agents voice support #900
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
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
953305e
Update attribute extraction to support dict as well as object.
tcdent ab8dad1
Adjust tests to match serialization format of `list[str]`. Patch JSON…
tcdent fd4b4f8
Instrumentor and wrappers for OpenAI responses.
tcdent 4c6b12f
Collect base usage attributes, too.
tcdent 0ebd74d
Merge branch 'main' into fix-openai-agents-counts
tcdent 5589ec6
Move Response attribute parsing to openai module. Move common attribu…
tcdent f2dca86
Include tags in parent span. Helpers for accessing global config and …
tcdent e1e3506
Add tags to an example.
tcdent d7a9f6f
Remove duplicate library attributes.
tcdent f30f43a
Pass OpenAI responses objects through our new instrumentor.
tcdent d98a5b0
Merge branch 'fix-openai-agents-counts' into openai-responses
tcdent e942f94
Incorporate common attributes, too.
tcdent 9b2c5f7
Add indexed PROMPT semconv to MessageAttributes. Provide reusable wra…
tcdent d58af58
Type checking.
tcdent 9d2d493
Test coverage for instrumentation.common
tcdent 12b559b
Type in method def should be string in case of missing import.
tcdent 9d77845
Wrap third party module imports from openai in try except block
tcdent d5cdb57
OpenAI instrumentation tests. (Relocated to openai_core to avoid impo…
tcdent 0df12c2
Merge branch 'main' into openai-responses
tcdent c430ad9
Merge branch 'openai-responses' into agents-voice
tcdent fe8932c
OpenAI Agents voice support
tcdent ef91208
Additional voice-specific fields.
tcdent 2830999
update pyproject.toml and uv.lock to use correct dependency
dot-agi bebc809
Remove `upload_object` helper.
tcdent b6cb5dd
Remove tag helpers.
tcdent 869a8af
Move agents example scripts.
tcdent 6427682
Merge branch 'main' into agents-voice
dot-agi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| """ | ||
| V4 API client for the AgentOps API. | ||
| This module provides the client for the V4 version of the AgentOps API. | ||
| """ | ||
| from typing import Optional, Union, Dict | ||
|
|
||
| from agentops.client.api.base import BaseApiClient | ||
| from agentops.exceptions import ApiServerException | ||
| from agentops.client.api.types import UploadedObjectResponse | ||
|
|
||
|
|
||
| class V4Client(BaseApiClient): | ||
| """Client for the AgentOps V4 API""" | ||
| auth_token: str | ||
|
|
||
| def set_auth_token(self, token: str): | ||
| """ | ||
| Set the authentication token for API requests. | ||
| Args: | ||
| token: The authentication token to set | ||
| """ | ||
| self.auth_token = token | ||
|
|
||
| def prepare_headers(self, custom_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]: | ||
tcdent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """ | ||
| Prepare headers for API requests. | ||
| Args: | ||
| custom_headers: Additional headers to include | ||
| Returns: | ||
| Headers dictionary with standard headers and any custom headers | ||
| """ | ||
| headers = { | ||
| "Authorization": f"Bearer {self.auth_token}", | ||
| } | ||
| if custom_headers: | ||
| headers.update(custom_headers) | ||
| return headers | ||
|
|
||
| def upload_object(self, body: Union[str, bytes]) -> UploadedObjectResponse: | ||
| """ | ||
| Upload an object to the API and return the response. | ||
| Args: | ||
| body: The object to upload, either as a string or bytes. | ||
| Returns: | ||
| UploadedObjectResponse: The response from the API after upload. | ||
| """ | ||
| if isinstance(body, bytes): | ||
| body = body.decode("utf-8") | ||
|
|
||
| response = self.post("/v4/objects/upload/", body, self.prepare_headers()) | ||
|
|
||
| if response.status_code != 200: | ||
| error_msg = f"Upload failed: {response.status_code}" | ||
| try: | ||
| error_data = response.json() | ||
| if "error" in error_data: | ||
| error_msg = error_data["error"] | ||
| except Exception: | ||
| pass | ||
| raise ApiServerException(error_msg) | ||
|
|
||
| try: | ||
| response_data = response.json() | ||
| return UploadedObjectResponse(**response_data) | ||
| except Exception as e: | ||
| raise ApiServerException(f"Failed to process upload response: {str(e)}") | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,4 +45,5 @@ | |
| "get_env_bool", | ||
| "get_env_int", | ||
| "get_env_list", | ||
| "get_tags_from_config", | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # AgentOps Instrumentation Common Module | ||
|
|
||
| The `agentops.instrumentation.common` module provides shared utilities for OpenTelemetry instrumentation across different LLM service providers. | ||
|
|
||
| ## Core Components | ||
|
|
||
| ### Attribute Handler Example | ||
|
|
||
| Attribute handlers extract data from method inputs and outputs: | ||
|
|
||
| ```python | ||
| from typing import Optional, Any, Tuple, Dict | ||
| from agentops.instrumentation.common.attributes import AttributeMap | ||
| from agentops.semconv import SpanAttributes | ||
|
|
||
| def my_attribute_handler(args: Optional[Tuple] = None, kwargs: Optional[Dict] = None, return_value: Optional[Any] = None) -> AttributeMap: | ||
| attributes = {} | ||
|
|
||
| # Extract attributes from kwargs (method inputs) | ||
| if kwargs: | ||
| if "model" in kwargs: | ||
| attributes[SpanAttributes.MODEL_NAME] = kwargs["model"] | ||
| # ... | ||
|
|
||
| # Extract attributes from return value (method outputs) | ||
| if return_value: | ||
| if hasattr(return_value, "model"): | ||
| attributes[SpanAttributes.LLM_RESPONSE_MODEL] = return_value.model | ||
| # ... | ||
|
|
||
| return attributes | ||
| ``` | ||
|
|
||
| ### `WrapConfig` Class | ||
|
|
||
| Config object defining how a method should be wrapped: | ||
|
|
||
| ```python | ||
| from agentops.instrumentation.common.wrappers import WrapConfig | ||
| from opentelemetry.trace import SpanKind | ||
|
|
||
| config = WrapConfig( | ||
| trace_name="llm.completion", # Name that will appear in trace spans | ||
| package="openai.resources", # Path to the module containing the class | ||
| class_name="Completions", # Name of the class containing the method | ||
| method_name="create", # Name of the method to wrap | ||
| handler=my_attribute_handler, # Function that extracts attributes | ||
| span_kind=SpanKind.CLIENT # Type of span to create | ||
| ) | ||
| ``` | ||
|
|
||
| ### Wrapping/Unwrapping Methods | ||
|
|
||
| ```python | ||
| from opentelemetry.trace import get_tracer | ||
| from agentops.instrumentation.common.wrappers import wrap, unwrap | ||
|
|
||
| # Create a tracer and wrap a method | ||
| tracer = get_tracer("openai", "0.0.0") | ||
| wrap(config, tracer) | ||
|
|
||
| # Later, unwrap the method | ||
| unwrap(config) | ||
| ``` | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from .attributes import AttributeMap, _extract_attributes_from_mapping | ||
|
|
||
| __all__ = [ | ||
| "AttributeMap", | ||
| "_extract_attributes_from_mapping", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| """Common attribute processing utilities shared across all instrumentors. | ||
|
|
||
| This module provides core utilities for extracting and formatting | ||
| OpenTelemetry-compatible attributes from span data. These functions | ||
| are provider-agnostic and used by all instrumentors in the AgentOps | ||
| package. | ||
|
|
||
| The module includes: | ||
|
|
||
| 1. Helper functions for attribute extraction and mapping | ||
| 2. Common attribute getters used across all providers | ||
| 3. Base trace and span attribute functions | ||
|
|
||
| All functions follow a consistent pattern: | ||
| - Accept span/trace data as input | ||
| - Process according to semantic conventions | ||
| - Return a dictionary of formatted attributes | ||
|
|
||
| These utilities ensure consistent attribute handling across different | ||
| LLM service instrumentors while maintaining separation of concerns. | ||
| """ | ||
| from typing import Dict, Any, Optional, List | ||
| from agentops.logging import logger | ||
| from agentops.helpers import safe_serialize, get_agentops_version | ||
| from agentops.semconv import ( | ||
| CoreAttributes, | ||
| InstrumentationAttributes, | ||
| WorkflowAttributes, | ||
| ) | ||
|
|
||
| # target_attribute_key: source_attribute | ||
| AttributeMap = Dict[str, Any] | ||
|
|
||
|
|
||
| def _extract_attributes_from_mapping(span_data: Any, attribute_mapping: AttributeMap) -> AttributeMap: | ||
| """Helper function to extract attributes based on a mapping. | ||
|
|
||
| Args: | ||
| span_data: The span data object or dict to extract attributes from | ||
| attribute_mapping: Dictionary mapping target attributes to source attributes | ||
|
|
||
| Returns: | ||
| Dictionary of extracted attributes | ||
| """ | ||
| attributes = {} | ||
| for target_attr, source_attr in attribute_mapping.items(): | ||
| if hasattr(span_data, source_attr): | ||
| # Use getattr to handle properties | ||
| value = getattr(span_data, source_attr) | ||
| elif isinstance(span_data, dict) and source_attr in span_data: | ||
| # Use direct key access for dicts | ||
| value = span_data[source_attr] | ||
| else: | ||
| continue | ||
|
|
||
| # Skip if value is None or empty | ||
| if value is None or (isinstance(value, (list, dict, str)) and not value): | ||
| continue | ||
|
|
||
| # Serialize complex objects | ||
| elif isinstance(value, (dict, list, object)) and not isinstance(value, (str, int, float, bool)): | ||
| value = safe_serialize(value) | ||
|
|
||
| attributes[target_attr] = value | ||
|
|
||
| return attributes | ||
|
|
||
|
|
||
| def get_common_attributes() -> AttributeMap: | ||
| """Get common instrumentation attributes used across traces and spans. | ||
|
|
||
| Returns: | ||
| Dictionary of common instrumentation attributes | ||
| """ | ||
| return { | ||
| InstrumentationAttributes.NAME: "agentops", | ||
| InstrumentationAttributes.VERSION: get_agentops_version(), | ||
| } | ||
|
|
||
|
|
||
| def get_base_trace_attributes(trace: Any) -> AttributeMap: | ||
| """Create the base attributes dictionary for an OpenTelemetry trace. | ||
|
|
||
| Args: | ||
| trace: The trace object to extract attributes from | ||
|
|
||
| Returns: | ||
| Dictionary containing base trace attributes | ||
| """ | ||
| if not hasattr(trace, 'trace_id'): | ||
| logger.warning("Cannot create trace attributes: missing trace_id") | ||
| return {} | ||
|
|
||
| attributes = { | ||
| WorkflowAttributes.WORKFLOW_NAME: trace.name, | ||
| CoreAttributes.TRACE_ID: trace.trace_id, | ||
| WorkflowAttributes.WORKFLOW_STEP_TYPE: "trace", | ||
| **get_common_attributes(), | ||
| } | ||
|
|
||
| # Add tags from the config to the trace attributes (these should only be added to the trace) | ||
| from agentops import get_client | ||
|
|
||
| config = get_client().config | ||
| tags = [] | ||
| if config.default_tags: | ||
| # `default_tags` can either be a `set` or a `list` | ||
| tags = list(config.default_tags) | ||
|
|
||
| attributes[CoreAttributes.TAGS] = tags | ||
|
|
||
| return attributes | ||
|
|
||
|
|
||
| def get_base_span_attributes(span: Any) -> AttributeMap: | ||
| """Create the base attributes dictionary for an OpenTelemetry span. | ||
|
|
||
| Args: | ||
| span: The span object to extract attributes from | ||
|
|
||
| Returns: | ||
| Dictionary containing base span attributes | ||
| """ | ||
| span_id = getattr(span, 'span_id', 'unknown') | ||
| trace_id = getattr(span, 'trace_id', 'unknown') | ||
| parent_id = getattr(span, 'parent_id', None) | ||
|
|
||
| attributes = { | ||
| CoreAttributes.TRACE_ID: trace_id, | ||
| CoreAttributes.SPAN_ID: span_id, | ||
| **get_common_attributes(), | ||
| } | ||
|
|
||
| if parent_id: | ||
| attributes[CoreAttributes.PARENT_ID] = parent_id | ||
|
|
||
| return attributes | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.