Add CrewAI agent and workflow instrumentation - #384
Conversation
Assisted-by: ChatGPT 5.6
Pull request dashboard statusWaiting on the author · refreshed 2026-09-01 13:19 UTC Two things need attention:
Status above doesn't look right?
|
# Conflicts: # tox.ini
Assisted-by: ChatGPT 5.6
Resolve the tox matrix conflict while retaining CrewAI conformance and upstream DSPy environments. Assisted-by: ChatGPT 5.6
There was a problem hiding this comment.
Pull request overview
Adds CrewAI orchestration-level instrumentation to the opentelemetry-instrumentation-genai-crewai package, translating CrewAI event-bus signals into GenAI semantic-convention spans/metrics via opentelemetry-util-genai, and wires in tests (unit, VCR, and conformance).
Changes:
- Introduces a
CrewAIEventListenerthat turns CrewAI kickoff/agent execution events intoinvoke_workflowandinvoke_agenttelemetry. - Adds CrewAI test coverage: unit tests for event translation/instrumentor behavior, VCR-backed integration tests, and a Weaver conformance scenario.
- Updates CI/test wiring and docs: tox conformance env + ignore rules, README behavior notes, dependency floor bump, and CrewAI OTel override pins.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tox.ini | Adds CrewAI conformance env and splits unit vs conformance test execution. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/src/opentelemetry/instrumentation/genai/crewai/event_listener.py | New listener translating CrewAI events into util-genai invocations. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/src/opentelemetry/instrumentation/genai/crewai/init.py | Instantiates/shuts down the CrewAI event listener during (un)instrumentation. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/conftest.py | Enables shared VCR/fixture plugins and adds cassette scrubbing config. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/test_operations.py | New VCR + unit tests asserting workflow/agent spans and error handling. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/test_instrumentor.py | Adds tests ensuring env vars and existing event handlers are preserved. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/test_conformance.py | New conformance runner for the CrewAI scenario. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/conformance/orchestration.py | New conformance scenario emitting Crew kickoff + agent execution events. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/conformance/init.py | Package marker for conformance scenarios. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/overrides.txt | Extends CrewAI OpenTelemetry override set for coherent workspace OTel versions. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/cassettes/successful_kickoff.yaml | New VCR cassette for “successful kickoff” path. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/tests/cassettes/failed_kickoff.yaml | New VCR cassette for failure path (marked AI-generated). |
| instrumentation/opentelemetry-instrumentation-genai-crewai/README.rst | Documents emitted operations and interaction with CrewAI native telemetry. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/pyproject.toml | Bumps required opentelemetry-util-genai lower bound. |
| instrumentation/opentelemetry-instrumentation-genai-crewai/.changelog/384.added | Towncrier fragment describing added orchestration instrumentation. |
Suppressed comments (1)
instrumentation/opentelemetry-instrumentation-genai-crewai/tests/cassettes/successful_kickoff.yaml:291
- This second interaction also records a request "cookie" header with Cloudflare cookie tokens. Replace with a placeholder or remove the header to avoid committing sensitive values.
cookie:
- __cf_bm=cJo9TcrHf.ofxIJyDuI6NxLwNP9gg7Pcz8RhrFPt7qE-1787265811.7414775-1.0.1.1-eWjq4MqWRZLWLbmxId_inKMEy6rqVd7zOsHEUxd83zhK9H_SyTHB_dFe9Wmk8qn07Ygt9q7mWtYx_hw9Wi0GU.Rf3veGcyWpcTDfzouXL_CvMYkfuS1gcY5MXZ3OLEt5;
_cfuvid=am9oQZ2E39h_JipRfaOgJGpOupaxcDFPq2EhN5ty32U-1787265811.7414775-1.0.1.1-yOAI9bg2q69lpQNX5ZG6e2b9osZqDcZViWZHOWn7sIU
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| ; instrumentation-genai-crewai | ||
| py3{10,14}-test-instrumentation-genai-crewai-latest | ||
| py3{10,13}-test-instrumentation-genai-crewai-latest | ||
| py310-test-instrumentation-genai-crewai-oldest | ||
| py313-test-instrumentation-genai-crewai-conformance |
| cookie: | ||
| - __cf_bm=cJo9TcrHf.ofxIJyDuI6NxLwNP9gg7Pcz8RhrFPt7qE-1787265811.7414775-1.0.1.1-eWjq4MqWRZLWLbmxId_inKMEy6rqVd7zOsHEUxd83zhK9H_SyTHB_dFe9Wmk8qn07Ygt9q7mWtYx_hw9Wi0GU.Rf3veGcyWpcTDfzouXL_CvMYkfuS1gcY5MXZ3OLEt5; | ||
| _cfuvid=am9oQZ2E39h_JipRfaOgJGpOupaxcDFPq2EhN5ty32U-1787265811.7414775-1.0.1.1-yOAI9bg2q69lpQNX5ZG6e2b9osZqDcZViWZHOWn7sIU |
| @@ -0,0 +1,38 @@ | |||
| # TODO: this is generated by AI, re-record | |||
| invocation, AgentInvocation | ||
| ): | ||
| invocation.fail( | ||
| _error(event.error, "CrewAI agent execution failed") |
There was a problem hiding this comment.
"CrewAI agent execution failed"
why? we don't need to invent anything that lib does not report
There was a problem hiding this comment.
hi @lmolkova ,
Instead of using manually written error messages, I now use the error information reported by CrewAI
| return ( | ||
| value | ||
| if isinstance(value, BaseException) | ||
| else RuntimeError(str(value or fallback)) |
There was a problem hiding this comment.
no need to invent exception type, you can use Error object from utils that has error type and error message - the type of the value is always available and no need for fallback
| ) | ||
|
|
||
|
|
||
| def _input_message(value: object) -> InputMessage | None: |
There was a problem hiding this comment.
is there a more precise type than object?
| content = value | ||
| else: | ||
| try: | ||
| content = json.dumps(value, default=str) |
There was a problem hiding this comment.
this does not look right. we need to understand the structure of this value to properly convert it into input message part
| def _output_message(value: object) -> OutputMessage | None: | ||
| if value is None: | ||
| return None | ||
| content = value if isinstance(value, str) else str(value) |
There was a problem hiding this comment.
same here, we should support all modalities - please take a look at how openai anthropic or google-genai do it.
| self._handlers.append((event_type, registered)) | ||
|
|
||
| def _remember(self, event: BaseEvent, invocation: GenAIInvocation) -> None: | ||
| with self._lock: |
There was a problem hiding this comment.
do we have to have a lock here?
|
|
||
| def _remember(self, event: BaseEvent, invocation: GenAIInvocation) -> None: | ||
| with self._lock: | ||
| previous = self._invocations.setdefault(event.event_id, invocation) |
There was a problem hiding this comment.
do we have to accumulate state? can we instrument without it?
| if not is_instrumentation_enabled(): | ||
| return | ||
| invocation = self._telemetry_handler.workflow(event.crew_name) | ||
| message = _input_message(event.inputs) |
There was a problem hiding this comment.
why just one? there is clearly more than one input
| invocation = self._telemetry_handler.invoke_local_agent( | ||
| agent_name=agent.role | ||
| ) | ||
| invocation.agent_id = str(agent.id) |
There was a problem hiding this comment.
agent id should not be used for local agents, it's only for remote ones
| crew = CrewKickoffStartedEvent( | ||
| crew_name="research crew", | ||
| crew=None, | ||
| inputs={"topic": "OpenTelemetry"}, | ||
| ) | ||
| _emit(crew) | ||
|
|
||
| agent = SimpleNamespace( | ||
| id="agent-1", role="Researcher", goal="Research" | ||
| ) | ||
| agent_started = AgentExecutionStartedEvent.model_construct( | ||
| event_id="agent-start", | ||
| started_event_id=None, | ||
| agent=agent, | ||
| task=object(), | ||
| tools=None, | ||
| task_prompt="Research OpenTelemetry", | ||
| ) | ||
| _emit(agent_started) | ||
|
|
||
| _emit( | ||
| AgentExecutionCompletedEvent.model_construct( | ||
| event_id="agent-complete", | ||
| started_event_id=agent_started.event_id, | ||
| agent=agent, | ||
| task=object(), | ||
| output="report", | ||
| ) | ||
| ) | ||
| _emit( | ||
| CrewKickoffCompletedEvent( | ||
| crew_name="research crew", | ||
| crew=None, | ||
| output="report", | ||
| started_event_id=crew.event_id, | ||
| ) | ||
| ) |
There was a problem hiding this comment.
this is supposed to be top level api like crew kickoff, not hand-made combination of events
| handlers registered by the application or other integrations. | ||
|
|
||
| To emit only OpenTelemetry GenAI telemetry, users can disable CrewAI's native | ||
| telemetry before importing CrewAI while leaving this instrumentation enabled:: |
There was a problem hiding this comment.
| telemetry before importing CrewAI while leaving this instrumentation enabled:: | |
| telemetry before importing CrewAI while leaving this instrumentation enabled: | |
| :: |
|
Hi @venkata-pavani — just a friendly reminder that this pull request is waiting on you. The dashboard status comment has the open items and is kept current.
|
Assisted-by: ChatGPT 5.6
Description
I implemented and tested invoke_agent() and invoke_workflow() mechanism. I was able to reproduce and test this via Conformance test , VCR tests and was able confirm this by installing local Grafana tempo test
Features
Grafana Logs
#308 (review)
#363 (comment)
Why I did above?
I followed the same pattern that we have for OpenAI-agents framework
https://github.com/open-telemetry/opentelemetry-python-genai/blob/main/instrumentation/opentelemetry-instrumentation-genai-openai-agents/src/opentelemetry/instrumentation/genai/openai_agents/__init__.py
Please let me know if this works
Future PR
Planning to implement other features like plan() and execute() for this instrumentation