Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions python/frameworks/swarm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## [0.1.9] - 2026-07-13
### Fixed
- Fix `crew.kickoff()` crashing with `TypeError: '_NotSpecified' object is not
iterable` when a `Task` is created without an explicit `context`. CrewAI
defaults `Task.context` to a `NOT_SPECIFIED` sentinel that is truthy but not
iterable, so the wrapper now guards on type before iterating. Affected `crewai`
0.203.2 and 1.15.1; the `context=[]` workaround is no longer needed. The
emitted `crew_tasks` payload is unchanged for every input that already worked.

Known limitation: an unspecified context is recorded as `null`, the same as an
explicit `None`/`[]`. CrewAI treats these differently — unspecified means "use
every upstream task's output", whereas `None`/`[]` mean "no context" — so
`null` does not distinguish the two. This matches the value the span carried
before CrewAI introduced the sentinel.
- Fix stale `__version__` in `traceai_crewai/version.py` (was `0.1.0` while the
published wheel was `0.1.8`), which mislabelled the instrumentation scope
version on every span.

## [0.1.6] - 2025-05-29
### Feature
- Updated dependencies to the latest versions.

## [0.1.5] - 2025-05-23
### Feature
- Added support for FutureAGI's protect

## [0.1.4] - 2025-05-08
### Feature
- Updated dependencies to the latest versions.

## [0.1.3] - 2025-04-14
### Changed
- Updated dependencies to the latest versions.
42 changes: 42 additions & 0 deletions python/frameworks/swarm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# TraceAI Instrumentation for OpenAI Swarm

This package provides OpenTelemetry instrumentation for the [OpenAI Swarm](https://github.com/openai/swarm) framework.

## Installation

```bash
pip install traceai-swarm
```

## Usage

```python
from swarm import Swarm, Agent
from traceai_swarm import SwarmInstrumentor

# Instrument Swarm
SwarmInstrumentor().instrument()

client = Swarm()

def transfer_to_agent_b():
return agent_b

agent_a = Agent(
name="Agent A",
instructions="You are a helpful agent.",
functions=[transfer_to_agent_b],
)

agent_b = Agent(
name="Agent B",
instructions="Only speak in Haikus.",
)

response = client.run(
agent=agent_a,
messages=[{"role": "user", "content": "I want to talk to agent B."}],
)

print(response.messages[-1]["content"])
```
19 changes: 19 additions & 0 deletions python/frameworks/swarm/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[tool.poetry]
name = "traceai-swarm"
version = "0.1.0"
description = "TraceAI Instrumentation for OpenAI Swarm"
authors = ["Future AGI <hello@futureagi.com>"]
readme = "README.md"
packages = [{include = "traceai_swarm"}]

[tool.poetry.dependencies]
python = "^3.9"
traceai-instrumentation = "^0.1.0"
wrapt = "^1.16.0"

[tool.poetry.group.dev.dependencies]
swarm = "^0.1.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
52 changes: 52 additions & 0 deletions python/frameworks/swarm/traceai_swarm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import logging
from importlib import import_module
from typing import Any, Collection

from fi_instrumentation import FITracer, TraceConfig
from opentelemetry import trace as trace_api
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore
from traceai_swarm._wrappers import (
_SwarmRunWrapper,
)
from traceai_swarm.version import __version__
from wrapt import wrap_function_wrapper

_instruments = ("swarm >= 0.1.0",)

logger = logging.getLogger(__name__)


class SwarmInstrumentor(BaseInstrumentor): # type: ignore
__slots__ = (
"_original_run",
"_tracer",
)

def instrumentation_dependencies(self) -> Collection[str]:
return _instruments

def _instrument(self, **kwargs: Any) -> None:
if not (tracer_provider := kwargs.get("tracer_provider")):
tracer_provider = trace_api.get_tracer_provider()
if not (config := kwargs.get("config")):
config = TraceConfig()
else:
assert isinstance(config, TraceConfig)
self._tracer = FITracer(
trace_api.get_tracer(__name__, __version__, tracer_provider),
config=config,
)

run_wrapper = _SwarmRunWrapper(tracer=self._tracer)
self._original_run = getattr(import_module("swarm.core"), "Swarm.run", None)
wrap_function_wrapper(
module="swarm.core",
name="Swarm.run",
wrapper=run_wrapper,
)

def _uninstrument(self, **kwargs: Any) -> None:
if self._original_run is not None:
swarm_module = import_module("swarm.core")
swarm_module.Swarm.run = self._original_run
self._original_run = None
38 changes: 38 additions & 0 deletions python/frameworks/swarm/traceai_swarm/_wrappers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import logging
from typing import Any, Callable

from fi_instrumentation import FITracer
from fi_instrumentation.events import Events
from opentelemetry.trace import SpanKind

logger = logging.getLogger(__name__)


class _SwarmRunWrapper:
def __init__(self, tracer: FITracer):
self._tracer = tracer

def __call__(
self,
wrapped: Callable[..., Any],
instance: Any,
args: Any,
kwargs: Any,
) -> Any:
span_name = "Swarm.run"
with self._tracer.start_as_current_span(
name=span_name,
kind=SpanKind.INTERNAL,
) as span:
try:
result = wrapped(*args, **kwargs)
span.set_attribute(Events.AGENT_NAME, "Swarm")
if result and hasattr(result, "messages"):
# Record the final messages
self._tracer.record_event(
span, Events.AGENT_RUN_SUCCESS, {"messages": result.messages}
)
return result
except Exception as e:
self._tracer.record_exception(span, e)
raise
1 change: 1 addition & 0 deletions python/frameworks/swarm/traceai_swarm/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.1.0"