agobs 1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. agobs-1.0/PKG-INFO +164 -0
  2. agobs-1.0/README.md +117 -0
  3. agobs-1.0/agentlens/__init__.py +48 -0
  4. agobs-1.0/agentlens/_diag.py +32 -0
  5. agobs-1.0/agentlens/adapters/__init__.py +16 -0
  6. agobs-1.0/agentlens/adapters/anthropic_agents.py +52 -0
  7. agobs-1.0/agentlens/adapters/autogen.py +61 -0
  8. agobs-1.0/agentlens/adapters/common.py +46 -0
  9. agobs-1.0/agentlens/adapters/crewai.py +66 -0
  10. agobs-1.0/agentlens/adapters/fastmcp.py +56 -0
  11. agobs-1.0/agentlens/adapters/langchain.py +183 -0
  12. agobs-1.0/agentlens/adapters/langgraph.py +17 -0
  13. agobs-1.0/agentlens/adapters/llamaindex.py +73 -0
  14. agobs-1.0/agentlens/adapters/mcp.py +47 -0
  15. agobs-1.0/agentlens/adapters/openai_agents.py +125 -0
  16. agobs-1.0/agentlens/adapters/pydanticai.py +92 -0
  17. agobs-1.0/agentlens/adapters/strands.py +92 -0
  18. agobs-1.0/agentlens/cli.py +57 -0
  19. agobs-1.0/agentlens/client.py +109 -0
  20. agobs-1.0/agentlens/events.py +124 -0
  21. agobs-1.0/agentlens/instrument/__init__.py +41 -0
  22. agobs-1.0/agentlens/instrument/_patch.py +119 -0
  23. agobs-1.0/agentlens/instrument/anthropic.py +32 -0
  24. agobs-1.0/agentlens/instrument/gemini.py +31 -0
  25. agobs-1.0/agentlens/instrument/groq.py +32 -0
  26. agobs-1.0/agentlens/instrument/litellm.py +33 -0
  27. agobs-1.0/agentlens/instrument/openai.py +32 -0
  28. agobs-1.0/agentlens/monitor.py +43 -0
  29. agobs-1.0/agentlens/server/__init__.py +0 -0
  30. agobs-1.0/agentlens/server/api.py +115 -0
  31. agobs-1.0/agentlens/server/app.py +68 -0
  32. agobs-1.0/agentlens/server/db.py +471 -0
  33. agobs-1.0/agentlens/server/docs.py +435 -0
  34. agobs-1.0/agentlens/server/llm.py +167 -0
  35. agobs-1.0/agentlens/server/pricing.py +100 -0
  36. agobs-1.0/agentlens/server/static/app.js +268 -0
  37. agobs-1.0/agentlens/server/static/fonts.css +46 -0
  38. agobs-1.0/agentlens/server/static/style.css +245 -0
  39. agobs-1.0/agentlens/server/templates/agents.html +1 -0
  40. agobs-1.0/agentlens/server/templates/base.html +38 -0
  41. agobs-1.0/agentlens/server/templates/cost.html +1 -0
  42. agobs-1.0/agentlens/server/templates/failures.html +1 -0
  43. agobs-1.0/agentlens/server/templates/memory.html +1 -0
  44. agobs-1.0/agentlens/server/templates/overview.html +1 -0
  45. agobs-1.0/agentlens/server/templates/run_detail.html +1 -0
  46. agobs-1.0/agentlens/server/templates/runs.html +1 -0
  47. agobs-1.0/agentlens/server/templates/tools.html +1 -0
  48. agobs-1.0/agentlens/storage.py +59 -0
  49. agobs-1.0/agentlens/tracing.py +178 -0
  50. agobs-1.0/agobs.egg-info/PKG-INFO +164 -0
  51. agobs-1.0/agobs.egg-info/SOURCES.txt +61 -0
  52. agobs-1.0/agobs.egg-info/dependency_links.txt +1 -0
  53. agobs-1.0/agobs.egg-info/entry_points.txt +2 -0
  54. agobs-1.0/agobs.egg-info/requires.txt +43 -0
  55. agobs-1.0/agobs.egg-info/top_level.txt +1 -0
  56. agobs-1.0/pyproject.toml +59 -0
  57. agobs-1.0/setup.cfg +4 -0
  58. agobs-1.0/tests/test_adapters.py +29 -0
  59. agobs-1.0/tests/test_api.py +33 -0
  60. agobs-1.0/tests/test_db.py +48 -0
  61. agobs-1.0/tests/test_events.py +35 -0
  62. agobs-1.0/tests/test_instrument.py +52 -0
  63. agobs-1.0/tests/test_tracing.py +49 -0
agobs-1.0/PKG-INFO ADDED
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: agobs
3
+ Version: 1.0
4
+ Summary: AgentObserve: local-first diagnostics & observability for AI agents. Answers 'Why did my agent behave this way?' with one line and a single SQLite file.
5
+ Author-email: Tharanika <sudhatharanika@gmail.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/Tharanika-R-Git/AgentLens
8
+ Keywords: agent,observability,tracing,diagnostics,llm,tools,mcp
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development :: Debuggers
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: fastapi>=0.100
16
+ Requires-Dist: uvicorn>=0.23
17
+ Requires-Dist: jinja2>=3.1
18
+ Requires-Dist: pydantic>=2.0
19
+ Requires-Dist: httpx>=0.24
20
+ Provides-Extra: langchain
21
+ Requires-Dist: langchain-core>=0.1; extra == "langchain"
22
+ Provides-Extra: langgraph
23
+ Requires-Dist: langgraph>=0.1; extra == "langgraph"
24
+ Provides-Extra: llamaindex
25
+ Requires-Dist: llama-index-core>=0.10; extra == "llamaindex"
26
+ Provides-Extra: crewai
27
+ Requires-Dist: crewai>=0.1; extra == "crewai"
28
+ Provides-Extra: autogen
29
+ Requires-Dist: pyautogen>=0.2; extra == "autogen"
30
+ Provides-Extra: pydanticai
31
+ Requires-Dist: pydantic-ai>=0.0.1; extra == "pydanticai"
32
+ Provides-Extra: openai-agents
33
+ Requires-Dist: openai-agents>=0.0.1; extra == "openai-agents"
34
+ Provides-Extra: mcp
35
+ Requires-Dist: mcp>=1.0; extra == "mcp"
36
+ Provides-Extra: fastmcp
37
+ Requires-Dist: fastmcp>=0.1; extra == "fastmcp"
38
+ Provides-Extra: all
39
+ Requires-Dist: langchain-core>=0.1; extra == "all"
40
+ Requires-Dist: langgraph>=0.1; extra == "all"
41
+ Requires-Dist: llama-index-core>=0.10; extra == "all"
42
+ Requires-Dist: crewai>=0.1; extra == "all"
43
+ Requires-Dist: pyautogen>=0.2; extra == "all"
44
+ Requires-Dist: mcp>=1.0; extra == "all"
45
+ Provides-Extra: dev
46
+ Requires-Dist: pytest>=7.0; extra == "dev"
47
+
48
+ # AgentLens
49
+
50
+ **Local-first diagnostics & observability for AI agents.** One line to integrate,
51
+ a single SQLite file, a built-in dashboard. AgentLens answers the question other
52
+ tools don't:
53
+
54
+ > **Why did my agent behave this way?**
55
+
56
+ Not a prompt tracker. Not a token dashboard. A diagnostics platform for *agent
57
+ behavior* — tool usage, memory access, workflow paths, decisions, failures,
58
+ retries, latency, and cost.
59
+
60
+ ```bash
61
+ pip install pyagentlens # core: store + dashboard + manual API + auto-patch
62
+ pip install "pyagentlens[langchain]" # framework adapters as extras (langgraph, crewai, …)
63
+ pip install "pyagentlens[all]" # every adapter
64
+ ```
65
+
66
+ > Package name on PyPI is `pyagentlens`; the import name is `agentlens`.
67
+
68
+ ```python
69
+ from agentlens import monitor
70
+ monitor.start() # auto-patches installed LLM SDKs — that's it
71
+ ```
72
+
73
+ > **Full docs:** see [`Guide.md`](./Guide.md) — in-depth reference for humans *and* AI
74
+ > coding agents (when-to-use-what tables, every API signature, per-framework
75
+ > snippets, troubleshooting, and an agent cheat sheet).
76
+
77
+ ```bash
78
+ agentlens ui # dashboard at http://127.0.0.1:7180
79
+ ```
80
+
81
+ No server to deploy. No cloud. No external services. Data lives in a hidden
82
+ `./.agentlens/agentlens.db` (SQLite, WAL).
83
+
84
+ ---
85
+
86
+ ## Three ways to instrument (mix freely)
87
+
88
+ ### 1. Zero-config auto-patch
89
+ `monitor.start()` detects and patches whatever is installed — **OpenAI,
90
+ Anthropic, Google Gemini, Groq, LiteLLM** — so every LLM call becomes an `llm`
91
+ span with tokens and cost, with no code changes.
92
+
93
+ ### 2. Manual (framework-agnostic, always available)
94
+ ```python
95
+ import agentlens
96
+ agentlens.init(project="research-bot")
97
+
98
+ with agentlens.trace_session("chat"):
99
+ with agentlens.trace_agent("planner", role="lead"):
100
+ agentlens.log_decision(options=["search", "answer"], chosen="search", reason="needs fresh info")
101
+ agentlens.log_memory("read", "user_prefs", hit=True)
102
+ agentlens.log_tool("web_search", args={"q": "ai news"}, result=[...], retry_count=1)
103
+ agentlens.log_llm(model="gpt-4o", input_tokens=1200, output_tokens=400)
104
+ ```
105
+ A tool/LLM logged with an `error=` (or a `trace_*` block that raises) is
106
+ automatically recorded as a failure.
107
+
108
+ ### 3. Framework adapters
109
+ | Framework | Import | Maturity |
110
+ |---|---|---|
111
+ | LangChain | `agentlens.adapters.langchain.AgentLensCallbackHandler` | full |
112
+ | LangGraph | `agentlens.adapters.langgraph.AgentLensCallbackHandler` | full (LangChain callbacks) |
113
+ | LlamaIndex | `agentlens.adapters.llamaindex.AgentLensLlamaHandler` | full |
114
+ | CrewAI | `agentlens.adapters.crewai` (`step_callback`, `instrument_tools`) | best-effort |
115
+ | AutoGen | `agentlens.adapters.autogen.instrument_agent` | best-effort |
116
+ | PydanticAI | `agentlens.adapters.pydanticai.instrument_agent` | best-effort |
117
+ | OpenAI Agents SDK | `agentlens.adapters.openai_agents.install` | best-effort |
118
+ | Strands Agents | `agentlens.adapters.strands.callback_handler` | best-effort |
119
+ | Anthropic Agent SDK | `agentlens.adapters.anthropic_agents.traced_async_tool` | tool-level |
120
+ | MCP | `agentlens.adapters.mcp.instrument_server` / `traced_tool` | tool-level |
121
+ | FastMCP | `agentlens.adapters.fastmcp.instrument_server` | tool-level |
122
+
123
+ Plus a universal `agentlens.adapters.wrap_tool(fn)` / `@traced_tool()` that works
124
+ with any framework or none.
125
+
126
+ ---
127
+
128
+ ## The dashboard
129
+
130
+ - **Overview** — runs, success/failure rate, cost, p50/p95 latency.
131
+ - **Runs → Run detail** — the **workflow execution graph** (span tree: session →
132
+ workflow → agent → tool/memory/llm/decision), click any node to inspect.
133
+ - **Agent Explorer** — runs, failures, latency, and attributed cost per agent.
134
+ - **Tool Explorer** — most used / slowest / most failed tools, retries.
135
+ - **Memory Explorer** — reads/writes/updates/deletes and read hit-rate.
136
+ - **Failure Explorer** — exceptions, messages, retries, jump to the failing run.
137
+ - **Cost Explorer** — token usage, cost by day, by model, most expensive agents.
138
+
139
+ ---
140
+
141
+ ## Try it now (no keys needed)
142
+
143
+ ```bash
144
+ pip install -e .
145
+ python examples/demo_agent.py
146
+ agentlens ui
147
+ ```
148
+
149
+ ## Design
150
+
151
+ Local-first, zero-config, framework-agnostic, async-safe. Observability code
152
+ never crashes your app (emit failures are swallowed). One `Span` model covers
153
+ every event; kind-specific data lives in `attributes`. Cost is computed from an
154
+ editable price book (`agentlens/server/pricing.py`).
155
+
156
+ CLI: `agentlens ui [--port 7180] [--host] [--backend-store-uri PATH]`,
157
+ `agentlens providers`, `agentlens version`. Override the port with
158
+ `AGENTLENS_PORT`.
159
+
160
+ **Single process** writes straight to local SQLite. For **multiple
161
+ processes/workers** sharing one dashboard, run `agentlens ui` once and point each
162
+ worker at it: `agentlens.init(project=..., tracking_uri="http://127.0.0.1:7180")`.
163
+
164
+ See [`Guide.md`](./Guide.md) for the complete reference.
agobs-1.0/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # AgentLens
2
+
3
+ **Local-first diagnostics & observability for AI agents.** One line to integrate,
4
+ a single SQLite file, a built-in dashboard. AgentLens answers the question other
5
+ tools don't:
6
+
7
+ > **Why did my agent behave this way?**
8
+
9
+ Not a prompt tracker. Not a token dashboard. A diagnostics platform for *agent
10
+ behavior* — tool usage, memory access, workflow paths, decisions, failures,
11
+ retries, latency, and cost.
12
+
13
+ ```bash
14
+ pip install pyagentlens # core: store + dashboard + manual API + auto-patch
15
+ pip install "pyagentlens[langchain]" # framework adapters as extras (langgraph, crewai, …)
16
+ pip install "pyagentlens[all]" # every adapter
17
+ ```
18
+
19
+ > Package name on PyPI is `pyagentlens`; the import name is `agentlens`.
20
+
21
+ ```python
22
+ from agentlens import monitor
23
+ monitor.start() # auto-patches installed LLM SDKs — that's it
24
+ ```
25
+
26
+ > **Full docs:** see [`Guide.md`](./Guide.md) — in-depth reference for humans *and* AI
27
+ > coding agents (when-to-use-what tables, every API signature, per-framework
28
+ > snippets, troubleshooting, and an agent cheat sheet).
29
+
30
+ ```bash
31
+ agentlens ui # dashboard at http://127.0.0.1:7180
32
+ ```
33
+
34
+ No server to deploy. No cloud. No external services. Data lives in a hidden
35
+ `./.agentlens/agentlens.db` (SQLite, WAL).
36
+
37
+ ---
38
+
39
+ ## Three ways to instrument (mix freely)
40
+
41
+ ### 1. Zero-config auto-patch
42
+ `monitor.start()` detects and patches whatever is installed — **OpenAI,
43
+ Anthropic, Google Gemini, Groq, LiteLLM** — so every LLM call becomes an `llm`
44
+ span with tokens and cost, with no code changes.
45
+
46
+ ### 2. Manual (framework-agnostic, always available)
47
+ ```python
48
+ import agentlens
49
+ agentlens.init(project="research-bot")
50
+
51
+ with agentlens.trace_session("chat"):
52
+ with agentlens.trace_agent("planner", role="lead"):
53
+ agentlens.log_decision(options=["search", "answer"], chosen="search", reason="needs fresh info")
54
+ agentlens.log_memory("read", "user_prefs", hit=True)
55
+ agentlens.log_tool("web_search", args={"q": "ai news"}, result=[...], retry_count=1)
56
+ agentlens.log_llm(model="gpt-4o", input_tokens=1200, output_tokens=400)
57
+ ```
58
+ A tool/LLM logged with an `error=` (or a `trace_*` block that raises) is
59
+ automatically recorded as a failure.
60
+
61
+ ### 3. Framework adapters
62
+ | Framework | Import | Maturity |
63
+ |---|---|---|
64
+ | LangChain | `agentlens.adapters.langchain.AgentLensCallbackHandler` | full |
65
+ | LangGraph | `agentlens.adapters.langgraph.AgentLensCallbackHandler` | full (LangChain callbacks) |
66
+ | LlamaIndex | `agentlens.adapters.llamaindex.AgentLensLlamaHandler` | full |
67
+ | CrewAI | `agentlens.adapters.crewai` (`step_callback`, `instrument_tools`) | best-effort |
68
+ | AutoGen | `agentlens.adapters.autogen.instrument_agent` | best-effort |
69
+ | PydanticAI | `agentlens.adapters.pydanticai.instrument_agent` | best-effort |
70
+ | OpenAI Agents SDK | `agentlens.adapters.openai_agents.install` | best-effort |
71
+ | Strands Agents | `agentlens.adapters.strands.callback_handler` | best-effort |
72
+ | Anthropic Agent SDK | `agentlens.adapters.anthropic_agents.traced_async_tool` | tool-level |
73
+ | MCP | `agentlens.adapters.mcp.instrument_server` / `traced_tool` | tool-level |
74
+ | FastMCP | `agentlens.adapters.fastmcp.instrument_server` | tool-level |
75
+
76
+ Plus a universal `agentlens.adapters.wrap_tool(fn)` / `@traced_tool()` that works
77
+ with any framework or none.
78
+
79
+ ---
80
+
81
+ ## The dashboard
82
+
83
+ - **Overview** — runs, success/failure rate, cost, p50/p95 latency.
84
+ - **Runs → Run detail** — the **workflow execution graph** (span tree: session →
85
+ workflow → agent → tool/memory/llm/decision), click any node to inspect.
86
+ - **Agent Explorer** — runs, failures, latency, and attributed cost per agent.
87
+ - **Tool Explorer** — most used / slowest / most failed tools, retries.
88
+ - **Memory Explorer** — reads/writes/updates/deletes and read hit-rate.
89
+ - **Failure Explorer** — exceptions, messages, retries, jump to the failing run.
90
+ - **Cost Explorer** — token usage, cost by day, by model, most expensive agents.
91
+
92
+ ---
93
+
94
+ ## Try it now (no keys needed)
95
+
96
+ ```bash
97
+ pip install -e .
98
+ python examples/demo_agent.py
99
+ agentlens ui
100
+ ```
101
+
102
+ ## Design
103
+
104
+ Local-first, zero-config, framework-agnostic, async-safe. Observability code
105
+ never crashes your app (emit failures are swallowed). One `Span` model covers
106
+ every event; kind-specific data lives in `attributes`. Cost is computed from an
107
+ editable price book (`agentlens/server/pricing.py`).
108
+
109
+ CLI: `agentlens ui [--port 7180] [--host] [--backend-store-uri PATH]`,
110
+ `agentlens providers`, `agentlens version`. Override the port with
111
+ `AGENTLENS_PORT`.
112
+
113
+ **Single process** writes straight to local SQLite. For **multiple
114
+ processes/workers** sharing one dashboard, run `agentlens ui` once and point each
115
+ worker at it: `agentlens.init(project=..., tracking_uri="http://127.0.0.1:7180")`.
116
+
117
+ See [`Guide.md`](./Guide.md) for the complete reference.
@@ -0,0 +1,48 @@
1
+ """AgentLens — local-first diagnostics & observability for AI agents.
2
+
3
+ Answers one question: *"Why did my agent behave this way?"*
4
+
5
+ Zero-config quickstart::
6
+
7
+ from agentlens import monitor
8
+ monitor.start() # auto-patches installed LLM SDKs
9
+ # ... run your agent ...
10
+
11
+ Manual instrumentation (framework-agnostic)::
12
+
13
+ import agentlens
14
+ agentlens.init(project="research-bot")
15
+ with agentlens.trace_agent("planner"):
16
+ agentlens.log_tool("web_search", args={"q": "..."}, result=[...], retry_count=1)
17
+ agentlens.log_memory("read", "user_prefs", hit=True)
18
+ agentlens.log_llm(model="gpt-4o", input_tokens=1200, output_tokens=400)
19
+
20
+ Then ``agentlens ui`` to explore the dashboard (default http://127.0.0.1:7180).
21
+ """
22
+ from . import monitor
23
+ from .client import flush, get_client, init
24
+ from .events import Span, SpanKind
25
+ from .tracing import (
26
+ current_session_id,
27
+ current_trace_id,
28
+ log_decision,
29
+ log_error,
30
+ log_llm,
31
+ log_memory,
32
+ log_retrieval,
33
+ log_tool,
34
+ trace,
35
+ trace_agent,
36
+ trace_session,
37
+ trace_workflow,
38
+ )
39
+
40
+ __version__ = "0.1.0"
41
+
42
+ __all__ = [
43
+ "init", "flush", "get_client", "monitor",
44
+ "trace", "trace_session", "trace_workflow", "trace_agent",
45
+ "current_trace_id", "current_session_id",
46
+ "log_llm", "log_tool", "log_memory", "log_decision", "log_retrieval", "log_error",
47
+ "Span", "SpanKind", "__version__",
48
+ ]
@@ -0,0 +1,32 @@
1
+ """Diagnostics for the framework adapters and auto-patchers.
2
+
3
+ Adapters hook into framework internals (callback signatures, method names) that
4
+ move between versions. When they drift the failure is silent — a span just stops
5
+ being captured. These helpers turn that silence into a visible
6
+ ``AgentLensWarning`` so version drift is noticed instead of producing empty
7
+ dashboards.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import warnings
12
+ from typing import Iterable
13
+
14
+
15
+ class AgentLensWarning(UserWarning):
16
+ """Emitted when an adapter/patcher can't hook something it expected to."""
17
+
18
+
19
+ def warn(message: str) -> None:
20
+ warnings.warn(f"[agentlens] {message}", AgentLensWarning, stacklevel=3)
21
+
22
+
23
+ def require_methods(obj: object, methods: Iterable[str], what: str) -> None:
24
+ """Warn if ``obj`` is missing every one of ``methods`` (so the wrapper would
25
+ silently capture nothing). ``methods`` is treated as "at least one must
26
+ exist"."""
27
+ present = [m for m in methods if callable(getattr(obj, m, None))]
28
+ if not present:
29
+ warn(
30
+ f"{what}: {type(obj).__name__} has none of {list(methods)} — "
31
+ f"that will not be captured (framework version drift?)"
32
+ )
@@ -0,0 +1,16 @@
1
+ """Framework adapters.
2
+
3
+ Auto-patching (``monitor.start()``) covers the raw LLM SDKs. Agent frameworks
4
+ expose their own callback/hook systems that carry far richer structure (agent
5
+ boundaries, tool calls, decisions), so each framework gets a dedicated adapter
6
+ here. Every adapter is import-time safe: importing this package never requires
7
+ any framework to be installed.
8
+
9
+ Common, framework-agnostic helpers live in ``common`` (``wrap_tool``,
10
+ ``traced_tool``) and work everywhere.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from .common import traced_tool, wrap_tool
15
+
16
+ __all__ = ["traced_tool", "wrap_tool"]
@@ -0,0 +1,52 @@
1
+ """Anthropic Agent SDK (``claude-agent-sdk``) adapter.
2
+
3
+ The Claude Agent SDK runs tools you define and streams messages. The most
4
+ reliable, version-stable instrumentation point is the tools themselves: wrap
5
+ each tool callable so its invocation logs a tool span (args, result, errors).
6
+ Raw Claude API calls the SDK makes are additionally captured by
7
+ ``monitor.start()`` auto-patching the underlying ``anthropic`` client.
8
+
9
+ Example::
10
+
11
+ from agentlens.adapters.anthropic_agents import traced_tool
12
+
13
+ @traced_tool("get_weather")
14
+ async def get_weather(args): ...
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import functools
19
+ import time
20
+ from typing import Any, Callable, Optional
21
+
22
+ from .common import traced_tool, wrap_tool
23
+
24
+ instrument_tool = wrap_tool
25
+
26
+
27
+ def traced_async_tool(name: Optional[str] = None):
28
+ """Async variant of :func:`traced_tool` for ``async def`` tool handlers."""
29
+ def deco(fn: Callable) -> Callable:
30
+ tool_name = name or getattr(fn, "__name__", "tool")
31
+
32
+ @functools.wraps(fn)
33
+ async def wrapper(*args, **kwargs):
34
+ from ..tracing import log_tool
35
+
36
+ t0 = time.time()
37
+ try:
38
+ result = await fn(*args, **kwargs)
39
+ except Exception as e:
40
+ log_tool(tool_name, args={"args": args, "kwargs": kwargs}, error=e,
41
+ duration_ms=(time.time() - t0) * 1000.0)
42
+ raise
43
+ log_tool(tool_name, args={"args": args, "kwargs": kwargs}, result=result,
44
+ duration_ms=(time.time() - t0) * 1000.0)
45
+ return result
46
+
47
+ return wrapper
48
+
49
+ return deco
50
+
51
+
52
+ __all__ = ["traced_tool", "traced_async_tool", "wrap_tool", "instrument_tool"]
@@ -0,0 +1,61 @@
1
+ """AutoGen (AG2 / pyautogen) adapter.
2
+
3
+ AutoGen conversable agents support registered reply/hook functions. This adapter
4
+ instruments an agent so each generated reply logs an agent span, and provides a
5
+ tool wrapper for function-calling tools.
6
+
7
+ Example::
8
+
9
+ from agentlens.adapters.autogen import instrument_agent
10
+ instrument_agent(assistant)
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+ from .. import client as _client
17
+ from ..events import Span, SpanKind, jsonable
18
+ from .common import wrap_tool
19
+
20
+ instrument_tool = wrap_tool
21
+
22
+
23
+ def instrument_agent(agent: Any) -> Any:
24
+ """Hook an AutoGen agent so its replies log agent spans. Uses
25
+ ``register_hook('process_message_before_send')`` when available, else wraps
26
+ ``generate_reply``."""
27
+ name = getattr(agent, "name", None) or type(agent).__name__
28
+
29
+ def _log(content: Any) -> None:
30
+ try:
31
+ ev = Span(project=_client.get_project(), kind=SpanKind.AGENT.value, name=name,
32
+ attributes={"agent_name": name, "output": jsonable(content)})
33
+ _client.get_client().log_event(ev.finish().model_dump())
34
+ except Exception:
35
+ pass
36
+
37
+ if hasattr(agent, "register_hook"):
38
+ def hook(sender=None, message=None, recipient=None, silent=None, **kw):
39
+ _log(message)
40
+ return message
41
+ try:
42
+ agent.register_hook("process_message_before_send", hook)
43
+ return agent
44
+ except Exception:
45
+ pass
46
+
47
+ # fallback: wrap generate_reply
48
+ original = getattr(agent, "generate_reply", None)
49
+ if callable(original):
50
+ import functools
51
+
52
+ @functools.wraps(original)
53
+ def wrapper(*a, **k):
54
+ reply = original(*a, **k)
55
+ _log(reply)
56
+ return reply
57
+ try:
58
+ agent.generate_reply = wrapper
59
+ except Exception:
60
+ pass
61
+ return agent
@@ -0,0 +1,46 @@
1
+ """Framework-agnostic instrumentation helpers.
2
+
3
+ These work with any agent framework (or none): wrap a plain callable as a tool
4
+ so each invocation logs a ``tool`` span — including its arguments, result,
5
+ duration, and any exception (which automatically feeds the Failure Explorer).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import functools
10
+ import time
11
+ from typing import Any, Callable, Optional
12
+
13
+
14
+ def wrap_tool(fn: Callable, name: Optional[str] = None) -> Callable:
15
+ """Return a wrapped callable that logs a ``tool`` span per call. Re-raises
16
+ the original exception after recording it, so behavior is unchanged."""
17
+ tool_name = name or getattr(fn, "__name__", None) or "tool"
18
+
19
+ @functools.wraps(fn)
20
+ def wrapper(*args, **kwargs):
21
+ from ..tracing import log_tool
22
+
23
+ t0 = time.time()
24
+ try:
25
+ result = fn(*args, **kwargs)
26
+ except Exception as e:
27
+ log_tool(tool_name, args={"args": args, "kwargs": kwargs}, error=e,
28
+ duration_ms=(time.time() - t0) * 1000.0)
29
+ raise
30
+ log_tool(tool_name, args={"args": args, "kwargs": kwargs}, result=result,
31
+ duration_ms=(time.time() - t0) * 1000.0)
32
+ return result
33
+
34
+ return wrapper
35
+
36
+
37
+ def traced_tool(name: Optional[str] = None):
38
+ """Decorator form of :func:`wrap_tool`.
39
+
40
+ @traced_tool()
41
+ def web_search(q): ...
42
+ """
43
+ def deco(fn: Callable) -> Callable:
44
+ return wrap_tool(fn, name)
45
+
46
+ return deco
@@ -0,0 +1,66 @@
1
+ """CrewAI adapter.
2
+
3
+ CrewAI agents/crews accept ``step_callback`` and ``task_callback`` hooks, and
4
+ their tools are plain callables. This adapter provides:
5
+
6
+ * ``step_callback`` / ``task_callback`` — pass to ``Agent(...)`` / ``Crew(...)``
7
+ to log agent-step and task spans,
8
+ * ``instrument_tools(tools)`` — wrap a list of tools so each invocation logs a
9
+ tool span (uses the framework-agnostic ``wrap_tool``).
10
+
11
+ Example::
12
+
13
+ from agentlens.adapters.crewai import step_callback, instrument_tools
14
+ agent = Agent(..., tools=instrument_tools(tools), step_callback=step_callback)
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from typing import Any, List
19
+
20
+ from ..events import SpanKind, jsonable
21
+ from ..tracing import _log
22
+ from .common import wrap_tool
23
+
24
+
25
+ def step_callback(step: Any) -> None:
26
+ """Log a CrewAI agent step as an agent span (best-effort field extraction).
27
+
28
+ Uses the contextvar-aware logger so the span nests under whatever trace is
29
+ active — wrap the crew kickoff in ``agentlens.trace_workflow(...)`` to keep a
30
+ crew's steps in one run instead of scattering them into top-level rows."""
31
+ try:
32
+ _log(SpanKind.AGENT.value, "crewai.step",
33
+ {"agent_name": getattr(step, "agent", None) and str(getattr(step, "agent")),
34
+ "output": jsonable(getattr(step, "output", None) or step)})
35
+ except Exception:
36
+ pass
37
+
38
+
39
+ def task_callback(task_output: Any) -> None:
40
+ """Log a CrewAI task result as a workflow span (nested under the active trace)."""
41
+ try:
42
+ _log(SpanKind.WORKFLOW.value, "crewai.task",
43
+ {"output": jsonable(getattr(task_output, "raw", None) or task_output)})
44
+ except Exception:
45
+ pass
46
+
47
+
48
+ def instrument_tools(tools: List[Any]) -> List[Any]:
49
+ """Wrap each tool's underlying callable so calls log tool spans. Works for
50
+ CrewAI ``BaseTool`` instances (wraps ``_run``/``run``) and plain functions."""
51
+ out = []
52
+ for t in tools or []:
53
+ for attr in ("_run", "run", "func"):
54
+ fn = getattr(t, attr, None)
55
+ if callable(fn):
56
+ name = getattr(t, "name", None) or getattr(fn, "__name__", "tool")
57
+ try:
58
+ setattr(t, attr, wrap_tool(fn, name))
59
+ except Exception:
60
+ pass
61
+ break
62
+ else:
63
+ if callable(t):
64
+ t = wrap_tool(t)
65
+ out.append(t)
66
+ return out
@@ -0,0 +1,56 @@
1
+ """FastMCP adapter.
2
+
3
+ FastMCP keeps registered tools in a tool manager; this adapter wraps each tool's
4
+ underlying function so calls log tool spans. Call ``instrument_server(mcp)``
5
+ after your tools are registered.
6
+
7
+ Example::
8
+
9
+ from fastmcp import FastMCP
10
+ from agentlens.adapters.fastmcp import instrument_server
11
+
12
+ mcp = FastMCP("demo")
13
+
14
+ @mcp.tool()
15
+ def add(a: int, b: int) -> int: return a + b
16
+
17
+ instrument_server(mcp)
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from typing import Any
22
+
23
+ from .anthropic_agents import traced_async_tool
24
+ from .common import traced_tool, wrap_tool
25
+
26
+ instrument_tool = wrap_tool
27
+
28
+
29
+ def instrument_server(mcp: Any) -> Any:
30
+ """Wrap each registered FastMCP tool's function with a tool-span logger."""
31
+ manager = getattr(mcp, "_tool_manager", None) or getattr(mcp, "tool_manager", None)
32
+ tools = None
33
+ if manager is not None:
34
+ tools = getattr(manager, "_tools", None) or getattr(manager, "tools", None)
35
+ tools = tools if tools is not None else getattr(mcp, "_tools", None)
36
+
37
+ items = []
38
+ if isinstance(tools, dict):
39
+ items = list(tools.items())
40
+ elif isinstance(tools, (list, tuple)):
41
+ items = [(getattr(t, "name", i), t) for i, t in enumerate(tools)]
42
+
43
+ for key, tool in items:
44
+ for attr in ("fn", "func", "handler", "callback"):
45
+ fn = getattr(tool, attr, None)
46
+ if callable(fn) and not getattr(fn, "__agentlens_patched__", False):
47
+ name = getattr(tool, "name", None) or str(key)
48
+ try:
49
+ setattr(tool, attr, wrap_tool(fn, name))
50
+ except Exception:
51
+ pass
52
+ break
53
+ return mcp
54
+
55
+
56
+ __all__ = ["traced_tool", "traced_async_tool", "wrap_tool", "instrument_tool", "instrument_server"]