-
Notifications
You must be signed in to change notification settings - Fork 268
Add conversation observability metadata #3270
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
8467f37
44e0efe
9d8db8f
015b17c
dd37fff
725d8f5
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 |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ | |
| ConversationCallbackType, | ||
| ConversationID, | ||
| StuckDetectionThresholds, | ||
| TraceMetadataValue, | ||
| ) | ||
| from openhands.sdk.conversation.visualizer import ( | ||
| ConversationVisualizerBase, | ||
|
|
@@ -666,6 +667,8 @@ def __init__( | |
| delete_on_close: bool = False, | ||
| tags: dict[str, str] | None = None, | ||
| user_id: str | None = None, | ||
| observability_metadata: dict[str, TraceMetadataValue] | None = None, | ||
| observability_tags: list[str] | None = None, | ||
| **_: object, | ||
| ) -> None: | ||
| """Remote conversation proxy that talks to an agent server. | ||
|
|
@@ -695,6 +698,8 @@ def __init__( | |
| tags: Optional key-value tags for the conversation. Keys must be | ||
| lowercase alphanumeric, values up to 256 characters. | ||
| user_id: Optional user ID to associate with observability traces | ||
| observability_metadata: Optional trace metadata for observability backends. | ||
| observability_tags: Optional root span tags for observability backends. | ||
| """ | ||
| super().__init__() # Initialize base class with span tracking | ||
| self.agent = agent | ||
|
|
@@ -759,8 +764,10 @@ def __init__( | |
| "plugins": [p.model_dump() for p in plugins] if plugins else None, | ||
| # Include hook_config for server-side hooks | ||
| "hook_config": hook_config.model_dump() if hook_config else None, | ||
| # Include tags if provided | ||
| # Include tags and observability metadata if provided | ||
| "tags": tags or {}, | ||
| "observability_metadata": observability_metadata or {}, | ||
| "observability_tags": observability_tags or [], | ||
|
Collaborator
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. 🟠 Important: The server-side |
||
| } | ||
| if stuck_detection_thresholds is not None: | ||
| # Convert to StuckDetectionThresholds if dict, then serialize | ||
|
|
@@ -884,7 +891,12 @@ def run_complete_callback(event: Event) -> None: | |
| secret_values: dict[str, SecretValue] = {k: v for k, v in secrets.items()} | ||
| self.update_secrets(secret_values) | ||
|
|
||
| self._start_observability_span(str(self._id), user_id=user_id) | ||
| self._start_observability_span( | ||
| str(self._id), | ||
| user_id=user_id, | ||
| metadata=observability_metadata, | ||
| tags=observability_tags, | ||
| ) | ||
| # All hooks (including SessionStart/SessionEnd) are executed server-side. | ||
| # hook_config is sent in the creation payload. | ||
| self.delete_on_close = delete_on_close | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -253,21 +253,27 @@ def __init__( | |
| name: str, | ||
| session_id: str | None = None, | ||
| user_id: str | None = None, | ||
| metadata: dict[str, Any] | None = None, | ||
| tags: list[str] | None = None, | ||
| ) -> None: | ||
| from lmnr import Laminar | ||
|
|
||
| # ``start_span`` returns a span without attaching it as the current | ||
| # OTel context; we'll restore it on every entry point via ``use_span``. | ||
| self.span = Laminar.start_span(name) | ||
| if session_id or user_id: | ||
| # ``set_trace_session_id`` / ``set_trace_user_id`` require an | ||
| # active span; briefly enter the span context to apply them. | ||
| if session_id or user_id or metadata or tags: | ||
| # These trace/span helpers require an active span; briefly enter | ||
| # the span context to apply conversation-level observability data. | ||
| with contextlib.suppress(Exception): | ||
| with Laminar.use_span(self.span): | ||
| if session_id: | ||
| Laminar.set_trace_session_id(session_id) | ||
| if user_id: | ||
| Laminar.set_trace_user_id(user_id) | ||
| if metadata: | ||
| Laminar.set_trace_metadata(metadata) | ||
|
Collaborator
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. 🟠 Important: These new metadata/tag setters run inside |
||
| if tags: | ||
| Laminar.set_span_tags(tags) | ||
| self._ended = False | ||
|
|
||
| def end(self) -> None: | ||
|
|
@@ -285,6 +291,8 @@ def start_root_span( | |
| name: str, | ||
| session_id: str | None = None, | ||
| user_id: str | None = None, | ||
| metadata: dict[str, Any] | None = None, | ||
| tags: list[str] | None = None, | ||
| ) -> RootSpan | None: | ||
| """Create a long-lived root span for an owning object. | ||
|
|
||
|
|
@@ -293,7 +301,13 @@ def start_root_span( | |
| if not should_enable_observability(): | ||
| return None | ||
| try: | ||
| return RootSpan(name, session_id=session_id, user_id=user_id) | ||
| return RootSpan( | ||
| name, | ||
| session_id=session_id, | ||
| user_id=user_id, | ||
| metadata=metadata, | ||
| tags=tags, | ||
| ) | ||
| except Exception: | ||
| logger.debug("Failed to create observability root span", exc_info=True) | ||
| return None | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.