fix(crewai): make traceai_crewai instrumentor compatible with crewai 1.x#168
Open
SuhaniNagpal7 wants to merge 1 commit into
Open
fix(crewai): make traceai_crewai instrumentor compatible with crewai 1.x#168SuhaniNagpal7 wants to merge 1 commit into
SuhaniNagpal7 wants to merge 1 commit into
Conversation
The traceai_crewai wrappers were written against crewai ~0.4x and break when the host application has upgraded to crewai 1.x. With the instrumentor installed, `Crew.kickoff()` raises before reaching the wrapped call — so the entire crew workflow fails the moment tracing is enabled. This patch fixes the two hard 1.x crashes plus two latent issues that surface when `crew.stream=True`, while preserving behavior on the older versions the instrumentor already supported. Verified end to end against crewai 1.14.4: instrumented Crew.kickoff ran cleanly, spans shipped to Future AGI Observe, agent run + task execute spans rendered with usage tokens (prompt 99 / completion 20 / total 119). What was breaking on crewai 1.x 1. `agent.i18n.prompt_file` no longer exists. `i18n` was demoted from an Agent field to a module-level singleton `I18N_DEFAULT` exposed by `crewai.utilities.i18n`. The wrapper's `crew_agents` dict-comp reads `agent.i18n.prompt_file` for every agent, raising AttributeError. Because the span block uses `record_exception=False, set_status_on_exception=False`, the exception propagates out and crashes every Crew.kickoff() call. 2. `Task.context` defaults to `_NotSpecified`, not `None`. In crewai 1.x `Task.context` is typed `list[Task] | None | _NotSpecified` and defaults to the sentinel. The wrapper's `if task.context else None` truthiness check passes for the sentinel, then the list-comprehension tries to iterate it and raises `TypeError: '_NotSpecified' object is not iterable`. Same crash surface as (1) — happens before `wrapped()` runs. 3. `crew.usage_metrics` can be None on streaming kickoffs. With `crew.stream=True`, crewai 1.x returns a `CrewStreamingOutput` immediately and never gets to `self.usage_metrics = self.calculate_usage_metrics()` before returning. The wrapper's `else` branch then reads `usage_metrics.prompt_tokens` and raises AttributeError. 4. `CrewStreamingOutput` has no `to_dict()`. The wrapper's `if crew_output_dict := crew_output.to_dict():` raises AttributeError when streaming returns the streaming-output wrapper instead of a CrewOutput. What this patch changes - Replace `agent.i18n.prompt_file` with a small helper that prefers `agent.i18n` (older crewai) and falls back to the `crewai.utilities.i18n.I18N_DEFAULT` singleton (1.x), with both imports wrapped in try/except so the package still loads on very old crewai where neither shape exists. - Replace the `Task.context` truthiness check with `isinstance(task.context, list)`. While there, rename the inner loop variable to `t` so it stops shadowing the outer `task` in the surrounding comprehension. - Change the usage-metrics fallback from `else:` to `elif usage_metrics is not None:` so streaming runs skip the object branch instead of crashing. - Replace the walrus on `crew_output.to_dict()` with a `getattr` + `callable` guard, falling through to `str(crew_output)` when absent. All four changes preserve the existing code paths on older crewai versions — only the failure modes new to 1.x are handled differently. The four span attributes the wrapper writes (input/output values, prompt/completion/total tokens, crew_agents/crew_tasks blobs, gen_ai.span.kind=CHAIN) are unchanged.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
traceai_crewaiwas written against crewai 0.4x. With the instrumentor installed on a host application that has upgraded to crewai 1.x,Crew.kickoff()raises before reaching the wrapped call — so the entire crew workflow fails the moment tracing is enabled. This PR fixes the two hard 1.x crashes plus two latent issues that surface on streaming kickoffs (crew.stream=True), with the minimum diff necessary. All four changes preserve behavior on the older crewai versions the package already supported.Verified end to end against crewai 1.14.4: instrumented
Crew.kickoffran cleanly, spans shipped to Future AGI Observe, agent run + task execute spans rendered with usage tokens (prompt 99 / completion 20 / total 119).What was breaking on crewai 1.x
1.
agent.i18n.prompt_fileno longer exists — crashes everyCrew.kickoff()In crewai 1.x,
i18nwas demoted from anAgentfield to a module-level singletonI18N_DEFAULTexposed bycrewai.utilities.i18n. The wrapper'screw_agentsdict-comp readsagent.i18n.prompt_filefor every agent increw.agents, which raisesAttributeError. Because the surrounding span block setsrecord_exception=False, set_status_on_exception=False, the exception propagates out beforewrapped()ever runs. Effect: everyCrew.kickoff()call crashes the momentCrewAIInstrumentor().instrument()is called.2.
Task.contextdefaults to_NotSpecified, notNone— crashes everyCrew.kickoff()In crewai 1.x
Task.contextis typedlist[Task] | None | _NotSpecifiedand defaults to the sentinel. The wrapper'sif task.context else Nonetruthiness check passes for the truthy sentinel, then the list-comp[task.description for task in task.context]raisesTypeError: '_NotSpecified' object is not iterable. Same crash surface as (1) — happens beforewrapped()runs.3.
crew.usage_metricscan beNoneon streaming kickoffsWith
crew.stream=True, crewai 1.x returns aCrewStreamingOutputimmediately and never reachesself.usage_metrics = self.calculate_usage_metrics()insideCrew.kickoffbefore returning. The wrapper's existingelsebranch then readsusage_metrics.prompt_tokensonNoneand raisesAttributeError.4.
CrewStreamingOutputhas noto_dict()The wrapper's
if crew_output_dict := crew_output.to_dict():raisesAttributeErrorwhen streaming returns the streaming-output wrapper instead of aCrewOutput.What this patch changes
All scoped to
python/frameworks/crewai/traceai_crewai/_wrappers.py:agent.i18n.prompt_file→ safe helper. Added a small_agent_i18n_prompt_file(agent)that prefersagent.i18n(older crewai) and falls back to thecrewai.utilities.i18n.I18N_DEFAULTsingleton (1.x). TheI18N_DEFAULTimport is wrapped intry/except ImportErrorso the package still loads on very old crewai where the singleton path doesn't exist.Task.contexttruthiness →isinstance(list)guard. Now[t.description for t in task.context] if isinstance(task.context, list) else None. Also renames the inner loop variable fromtasktotso it stops shadowing the outertaskin the surrounding comprehension — a latent bug that was just masked by the earlier crash.else:→elif usage_metrics is not None:. Streaming runs skip the object branch instead of crashing onNone.prompt_tokens. The earlierisinstance(usage_metrics, dict)branch is unchanged.Walrus on
crew_output.to_dict()→getattr+callableguard.to_dict = getattr(crew_output, 'to_dict', None); crew_output_dict = to_dict() if callable(to_dict) else None— falls through tostr(crew_output)when absent.What's NOT changed
I deliberately kept the diff to the four 1.x regressions. The following were considered and dropped as out-of-scope:
isinstance(args[0], Agent)in_get_input_value— would degrade for non-AgentBaseAgentsubclasses (LiteAgent, agent adapters), but isn't a crash on a normal CrewAIAgent. Future-proofing, not a 1.x regression.span.set_attribute("function_calling_llm", instance.function_calling_llm)—function_calling_llmis now anLLMobject in 1.x, but OTel silently drops non-primitive attribute values. No crash; same behavior as 0.x where it was a LangChain ChatModel.set_attribute(OUTPUT_VALUE, ...)line in_ToolUseWrapper. Pre-existing dead code, not in scope.Crew.kickoff,Task._execute_core,ToolUsage._use). Model name and cost come from runningOpenAIInstrumentor(or equivalent provider instrumentor) alongside this one. Same pattern as the other framework instrumentors in this repo.Test plan
python -m py_compile python/frameworks/crewai/traceai_crewai/_wrappers.py— syntax clean.crewai==1.14.4,fi-instrumentation-otel==1.0.0, andtraceai-crewaiinstalled editable from this branch.crew.kickoff()against OpenAIgpt-4o-mini. Before fix: crashes with_NotSpecifiedTypeError. After fix: runs to completion, returns model output, no exceptions.Crew.kickoffroot span: status OK, duration 3.4s, tokens prompt=99 / completion=20 / total=119,gen_ai.span.kind=CHAIN,crew_agentsandcrew_tasksblobs populated,crew_tasks[].context = null(correctly handled_NotSpecifiedsentinel),crew_agents[].i18n = null(correctly handled missing field),output.value= the agent's generated text.Task._execute_corechild span present and parented correctly.Files changed
python/frameworks/crewai/traceai_crewai/_wrappers.py— 28 insertions, 6 deletions.