-
Notifications
You must be signed in to change notification settings - Fork 580
feat(ai): add claude code agents sdk integration #5316
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: master
Are you sure you want to change the base?
Conversation
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨Span Streaming
Other
Bug Fixes 🐛Google Genai
Span Streaming
Other
Internal Changes 🔧Fastmcp
Mcp
Other
🤖 This preview updates automatically when you update the PR. |
|
Excited about this!
|
| chat_span = get_start_span_function()( | ||
| op=OP.GEN_AI_CHAT, | ||
| name=f"claude-agent-sdk query {model}".strip(), | ||
| origin=ClaudeAgentSDKIntegration.origin, | ||
| ) | ||
| chat_span.__enter__() | ||
|
|
||
| with capture_internal_exceptions(): | ||
| _set_span_input_data(chat_span, prompt, options, integration) | ||
|
|
||
| collected_messages = [] | ||
| try: | ||
| async for message in original_func( | ||
| prompt=prompt, options=options, **kwargs | ||
| ): | ||
| collected_messages.append(message) | ||
| yield message | ||
| except Exception as exc: | ||
| _capture_exception(exc) | ||
| raise | ||
| finally: | ||
| with capture_internal_exceptions(): | ||
| _set_span_output_data(chat_span, collected_messages, integration) | ||
| chat_span.__exit__(None, None, None) | ||
|
|
||
| with capture_internal_exceptions(): | ||
| _process_tool_executions(collected_messages, integration) | ||
|
|
||
| with capture_internal_exceptions(): | ||
| _end_invoke_agent_span(invoke_span, collected_messages, integration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does your agent know how to use a context manager 😄
| chat_span = get_start_span_function()( | |
| op=OP.GEN_AI_CHAT, | |
| name=f"claude-agent-sdk query {model}".strip(), | |
| origin=ClaudeAgentSDKIntegration.origin, | |
| ) | |
| chat_span.__enter__() | |
| with capture_internal_exceptions(): | |
| _set_span_input_data(chat_span, prompt, options, integration) | |
| collected_messages = [] | |
| try: | |
| async for message in original_func( | |
| prompt=prompt, options=options, **kwargs | |
| ): | |
| collected_messages.append(message) | |
| yield message | |
| except Exception as exc: | |
| _capture_exception(exc) | |
| raise | |
| finally: | |
| with capture_internal_exceptions(): | |
| _set_span_output_data(chat_span, collected_messages, integration) | |
| chat_span.__exit__(None, None, None) | |
| with capture_internal_exceptions(): | |
| _process_tool_executions(collected_messages, integration) | |
| with capture_internal_exceptions(): | |
| _end_invoke_agent_span(invoke_span, collected_messages, integration) | |
| chat_span = get_start_span_function()( | |
| op=OP.GEN_AI_CHAT, | |
| name=f"claude-agent-sdk query {model}".strip(), | |
| origin=ClaudeAgentSDKIntegration.origin, | |
| ) | |
| with chat_span() as span: | |
| with capture_internal_exceptions(): | |
| _set_span_input_data(span, prompt, options, integration) | |
| collected_messages = [] | |
| try: | |
| async for message in original_func( | |
| prompt=prompt, options=options, **kwargs | |
| ): | |
| collected_messages.append(message) | |
| yield message | |
| except Exception as exc: | |
| _capture_exception(exc) | |
| raise | |
| finally: | |
| with capture_internal_exceptions(): | |
| _set_span_output_data(span, collected_messages, integration) | |
| with capture_internal_exceptions(): | |
| _process_tool_executions(collected_messages, integration) | |
| with capture_internal_exceptions(): | |
| _end_invoke_agent_span(invoke_span, collected_messages, integration) |
| model = getattr(options, "model", "") if options else "" | ||
| invoke_span = _start_invoke_agent_span(prompt, options, integration) | ||
|
|
||
| chat_span = get_start_span_function()( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an invoke_agent span active here, why not just always start a span?
| messages = self._sentry_query_context.get("messages", []) | ||
| with capture_internal_exceptions(): | ||
| _set_span_output_data(chat_span, messages, integration) | ||
| chat_span.__exit__(None, None, None) | ||
| with capture_internal_exceptions(): | ||
| _end_invoke_agent_span(invoke_span, messages, integration) | ||
| self._sentry_query_context = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exiting spans has caused many uncaught exceptions already 😄
| messages = self._sentry_query_context.get("messages", []) | |
| with capture_internal_exceptions(): | |
| _set_span_output_data(chat_span, messages, integration) | |
| chat_span.__exit__(None, None, None) | |
| with capture_internal_exceptions(): | |
| _end_invoke_agent_span(invoke_span, messages, integration) | |
| self._sentry_query_context = {} | |
| messages = self._sentry_query_context.get("messages", []) | |
| with capture_internal_exceptions(): | |
| _set_span_output_data(chat_span, messages, integration) | |
| chat_span.__exit__(None, None, None) | |
| _end_invoke_agent_span(invoke_span, messages, integration) | |
| self._sentry_query_context = {} |
| tool_uses = {} | ||
| tool_results = {} | ||
|
|
||
| for message in messages: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
narrower than Anthropic message types, so this looks suspicious.
|
|
||
| def _extract_message_data(messages: list) -> dict: | ||
| """Extract relevant data from a list of messages.""" | ||
| data = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use a class type instead of a dictionary for passing info between functions, stronger typing.
| ) | ||
|
|
||
| if _should_include_prompts(integration): | ||
| messages = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
system prompts go in their own attribute.
| async for message in original_func( | ||
| prompt=prompt, options=options, **kwargs | ||
| ): | ||
| collected_messages.append(message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
your collecting user-held references here, leading to possible race conditions.
| def test_extract_text_returns_none_for_non_assistant(): | ||
| result = make_result_message(usage=None) | ||
| assert _extract_text_from_message(result) is None | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need all these unit tests, more work in the long run.
Instead, call the userspace API, and parameterize on as many input schemas as possible to achieve good test coverage.
Add instrumentation for the claude-agent-sdk package, which provides
a Python interface to interact with Claude Code CLI.
The integration captures:
Instrumented methods:
Closes TET-1743
Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com