nodeloom-sdk 0.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.
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: nodeloom-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for NodeLoom AI agent monitoring and telemetry
5
+ Author-email: NodeLoom <sdk@nodeloom.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://nodeloom.io
8
+ Project-URL: Documentation, https://docs.nodeloom.io/sdk/python
9
+ Project-URL: Repository, https://github.com/nodeloom/nodeloom-python-sdk
10
+ Keywords: nodeloom,telemetry,monitoring,ai,agents,observability
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: System :: Monitoring
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: requests>=2.28.0
25
+ Provides-Extra: langchain
26
+ Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
27
+ Provides-Extra: crewai
28
+ Requires-Dist: crewai>=0.1.0; extra == "crewai"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
32
+
33
+ # NodeLoom Python SDK
34
+
35
+ Python SDK for instrumenting AI agents and sending telemetry to [NodeLoom](https://nodeloom.io).
36
+
37
+ ## Features
38
+
39
+ - Fire-and-forget telemetry that never blocks or crashes your application
40
+ - Automatic batching and retry with exponential backoff
41
+ - Context manager support for traces and spans
42
+ - Built-in integrations for LangChain and CrewAI
43
+ - Thread-safe client (individual traces/spans are single-threaded)
44
+ - Bounded in-memory queue prevents unbounded memory growth
45
+ - Configurable via constructor arguments
46
+
47
+ ## Requirements
48
+
49
+ - Python 3.9+
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install nodeloom-sdk
55
+ ```
56
+
57
+ With LangChain integration:
58
+
59
+ ```bash
60
+ pip install nodeloom-sdk[langchain]
61
+ ```
62
+
63
+ With CrewAI integration:
64
+
65
+ ```bash
66
+ pip install nodeloom-sdk[crewai]
67
+ ```
68
+
69
+ ## Quick Start
70
+
71
+ ```python
72
+ from nodeloom import NodeLoom, SpanType
73
+
74
+ client = NodeLoom(api_key="sdk_your_api_key")
75
+
76
+ with client.trace("my-agent", input={"query": "What is NodeLoom?"}) as trace:
77
+ with trace.span("llm-call", type=SpanType.LLM) as span:
78
+ span.set_input({"messages": [{"role": "user", "content": "What is NodeLoom?"}]})
79
+ # ... call your LLM ...
80
+ span.set_output({"text": "NodeLoom is an AI agent operations platform."})
81
+ span.set_token_usage(prompt=15, completion=20, model="gpt-4o")
82
+
83
+ client.shutdown()
84
+ ```
85
+
86
+ ## Traces and Spans
87
+
88
+ A **trace** represents a single end-to-end agent execution. A **span** represents a unit of work within a trace (an LLM call, tool invocation, retrieval step, etc.).
89
+
90
+ ### Span Types
91
+
92
+ | Type | Description |
93
+ |------|-------------|
94
+ | `SpanType.LLM` | Language model call |
95
+ | `SpanType.TOOL` | Tool or function invocation |
96
+ | `SpanType.RETRIEVAL` | Vector search or data retrieval |
97
+ | `SpanType.CHAIN` | Pipeline or chain of steps |
98
+ | `SpanType.AGENT` | Sub-agent invocation |
99
+ | `SpanType.CUSTOM` | User-defined operation |
100
+
101
+ ### Nested Spans
102
+
103
+ ```python
104
+ with client.trace("my-agent") as trace:
105
+ with trace.span("agent-step", type=SpanType.AGENT) as parent:
106
+ with trace.span("llm-call", type=SpanType.LLM, parent_span_id=parent.span_id) as child:
107
+ child.set_output({"response": "..."})
108
+ child.set_token_usage(prompt=10, completion=20, model="gpt-4o")
109
+ ```
110
+
111
+ ### Standalone Events
112
+
113
+ ```python
114
+ client.event("guardrail_triggered", level=EventLevel.WARN, data={"rule": "pii_detected"})
115
+ ```
116
+
117
+ ### Error Handling
118
+
119
+ Traces and spans used as context managers automatically catch exceptions, mark the span/trace as `ERROR`, and re-raise:
120
+
121
+ ```python
122
+ with client.trace("my-agent") as trace:
123
+ with trace.span("risky-call", type=SpanType.TOOL) as span:
124
+ raise ValueError("something went wrong")
125
+ # span is automatically marked as ERROR
126
+ # trace is automatically marked as ERROR
127
+ ```
128
+
129
+ You can also set errors manually:
130
+
131
+ ```python
132
+ span.set_error("Connection timeout")
133
+ trace.end(status=TraceStatus.ERROR, error="Agent failed")
134
+ ```
135
+
136
+ ## LangChain Integration
137
+
138
+ ```python
139
+ from nodeloom import NodeLoom
140
+ from nodeloom.integrations.langchain import NodeLoomCallbackHandler
141
+
142
+ client = NodeLoom(api_key="sdk_your_api_key")
143
+ handler = NodeLoomCallbackHandler(client)
144
+
145
+ # Pass the handler to any LangChain chain, agent, or LLM
146
+ result = chain.invoke(input, config={"callbacks": [handler]})
147
+
148
+ client.shutdown()
149
+ ```
150
+
151
+ The callback handler automatically instruments LLM calls, chain runs, tool invocations, and retriever queries with proper parent-child span relationships.
152
+
153
+ ## CrewAI Integration
154
+
155
+ ### Decorator
156
+
157
+ ```python
158
+ from nodeloom import NodeLoom
159
+ from nodeloom.integrations.crewai import instrument_crew
160
+
161
+ client = NodeLoom(api_key="sdk_your_api_key")
162
+
163
+ @instrument_crew(client, agent_name="my-crew", agent_version="1.0.0")
164
+ def run_crew():
165
+ crew = Crew(agents=[...], tasks=[...])
166
+ return crew.kickoff()
167
+
168
+ run_crew()
169
+ client.shutdown()
170
+ ```
171
+
172
+ ### Manual Instrumentation
173
+
174
+ ```python
175
+ from nodeloom.integrations.crewai import CrewAIInstrumentation
176
+
177
+ inst = CrewAIInstrumentation(client)
178
+ with inst.trace_crew("my-crew") as ctx:
179
+ with ctx.task("research", agent="researcher") as span:
180
+ result = do_research()
181
+ span.set_output({"result": result})
182
+ ```
183
+
184
+ ## Configuration
185
+
186
+ | Parameter | Default | Description |
187
+ |-----------|---------|-------------|
188
+ | `api_key` | *required* | SDK API key (starts with `sdk_`) |
189
+ | `endpoint` | `https://api.nodeloom.io` | NodeLoom API base URL |
190
+ | `environment` | `production` | Deployment environment label |
191
+ | `batch_size` | `100` | Max events per batch |
192
+ | `flush_interval` | `5.0` | Seconds between automatic flushes |
193
+ | `max_retries` | `3` | Retry attempts for failed requests |
194
+ | `queue_max_size` | `10000` | Max queued events before dropping |
195
+ | `timeout` | `10.0` | HTTP request timeout in seconds |
196
+ | `enabled` | `True` | Set to `False` to disable telemetry |
197
+
198
+ ## Running Tests
199
+
200
+ ```bash
201
+ pip install -e ".[dev]"
202
+ pytest
203
+ ```
204
+
205
+ ## License
206
+
207
+ MIT
@@ -0,0 +1,175 @@
1
+ # NodeLoom Python SDK
2
+
3
+ Python SDK for instrumenting AI agents and sending telemetry to [NodeLoom](https://nodeloom.io).
4
+
5
+ ## Features
6
+
7
+ - Fire-and-forget telemetry that never blocks or crashes your application
8
+ - Automatic batching and retry with exponential backoff
9
+ - Context manager support for traces and spans
10
+ - Built-in integrations for LangChain and CrewAI
11
+ - Thread-safe client (individual traces/spans are single-threaded)
12
+ - Bounded in-memory queue prevents unbounded memory growth
13
+ - Configurable via constructor arguments
14
+
15
+ ## Requirements
16
+
17
+ - Python 3.9+
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install nodeloom-sdk
23
+ ```
24
+
25
+ With LangChain integration:
26
+
27
+ ```bash
28
+ pip install nodeloom-sdk[langchain]
29
+ ```
30
+
31
+ With CrewAI integration:
32
+
33
+ ```bash
34
+ pip install nodeloom-sdk[crewai]
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```python
40
+ from nodeloom import NodeLoom, SpanType
41
+
42
+ client = NodeLoom(api_key="sdk_your_api_key")
43
+
44
+ with client.trace("my-agent", input={"query": "What is NodeLoom?"}) as trace:
45
+ with trace.span("llm-call", type=SpanType.LLM) as span:
46
+ span.set_input({"messages": [{"role": "user", "content": "What is NodeLoom?"}]})
47
+ # ... call your LLM ...
48
+ span.set_output({"text": "NodeLoom is an AI agent operations platform."})
49
+ span.set_token_usage(prompt=15, completion=20, model="gpt-4o")
50
+
51
+ client.shutdown()
52
+ ```
53
+
54
+ ## Traces and Spans
55
+
56
+ A **trace** represents a single end-to-end agent execution. A **span** represents a unit of work within a trace (an LLM call, tool invocation, retrieval step, etc.).
57
+
58
+ ### Span Types
59
+
60
+ | Type | Description |
61
+ |------|-------------|
62
+ | `SpanType.LLM` | Language model call |
63
+ | `SpanType.TOOL` | Tool or function invocation |
64
+ | `SpanType.RETRIEVAL` | Vector search or data retrieval |
65
+ | `SpanType.CHAIN` | Pipeline or chain of steps |
66
+ | `SpanType.AGENT` | Sub-agent invocation |
67
+ | `SpanType.CUSTOM` | User-defined operation |
68
+
69
+ ### Nested Spans
70
+
71
+ ```python
72
+ with client.trace("my-agent") as trace:
73
+ with trace.span("agent-step", type=SpanType.AGENT) as parent:
74
+ with trace.span("llm-call", type=SpanType.LLM, parent_span_id=parent.span_id) as child:
75
+ child.set_output({"response": "..."})
76
+ child.set_token_usage(prompt=10, completion=20, model="gpt-4o")
77
+ ```
78
+
79
+ ### Standalone Events
80
+
81
+ ```python
82
+ client.event("guardrail_triggered", level=EventLevel.WARN, data={"rule": "pii_detected"})
83
+ ```
84
+
85
+ ### Error Handling
86
+
87
+ Traces and spans used as context managers automatically catch exceptions, mark the span/trace as `ERROR`, and re-raise:
88
+
89
+ ```python
90
+ with client.trace("my-agent") as trace:
91
+ with trace.span("risky-call", type=SpanType.TOOL) as span:
92
+ raise ValueError("something went wrong")
93
+ # span is automatically marked as ERROR
94
+ # trace is automatically marked as ERROR
95
+ ```
96
+
97
+ You can also set errors manually:
98
+
99
+ ```python
100
+ span.set_error("Connection timeout")
101
+ trace.end(status=TraceStatus.ERROR, error="Agent failed")
102
+ ```
103
+
104
+ ## LangChain Integration
105
+
106
+ ```python
107
+ from nodeloom import NodeLoom
108
+ from nodeloom.integrations.langchain import NodeLoomCallbackHandler
109
+
110
+ client = NodeLoom(api_key="sdk_your_api_key")
111
+ handler = NodeLoomCallbackHandler(client)
112
+
113
+ # Pass the handler to any LangChain chain, agent, or LLM
114
+ result = chain.invoke(input, config={"callbacks": [handler]})
115
+
116
+ client.shutdown()
117
+ ```
118
+
119
+ The callback handler automatically instruments LLM calls, chain runs, tool invocations, and retriever queries with proper parent-child span relationships.
120
+
121
+ ## CrewAI Integration
122
+
123
+ ### Decorator
124
+
125
+ ```python
126
+ from nodeloom import NodeLoom
127
+ from nodeloom.integrations.crewai import instrument_crew
128
+
129
+ client = NodeLoom(api_key="sdk_your_api_key")
130
+
131
+ @instrument_crew(client, agent_name="my-crew", agent_version="1.0.0")
132
+ def run_crew():
133
+ crew = Crew(agents=[...], tasks=[...])
134
+ return crew.kickoff()
135
+
136
+ run_crew()
137
+ client.shutdown()
138
+ ```
139
+
140
+ ### Manual Instrumentation
141
+
142
+ ```python
143
+ from nodeloom.integrations.crewai import CrewAIInstrumentation
144
+
145
+ inst = CrewAIInstrumentation(client)
146
+ with inst.trace_crew("my-crew") as ctx:
147
+ with ctx.task("research", agent="researcher") as span:
148
+ result = do_research()
149
+ span.set_output({"result": result})
150
+ ```
151
+
152
+ ## Configuration
153
+
154
+ | Parameter | Default | Description |
155
+ |-----------|---------|-------------|
156
+ | `api_key` | *required* | SDK API key (starts with `sdk_`) |
157
+ | `endpoint` | `https://api.nodeloom.io` | NodeLoom API base URL |
158
+ | `environment` | `production` | Deployment environment label |
159
+ | `batch_size` | `100` | Max events per batch |
160
+ | `flush_interval` | `5.0` | Seconds between automatic flushes |
161
+ | `max_retries` | `3` | Retry attempts for failed requests |
162
+ | `queue_max_size` | `10000` | Max queued events before dropping |
163
+ | `timeout` | `10.0` | HTTP request timeout in seconds |
164
+ | `enabled` | `True` | Set to `False` to disable telemetry |
165
+
166
+ ## Running Tests
167
+
168
+ ```bash
169
+ pip install -e ".[dev]"
170
+ pytest
171
+ ```
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nodeloom-sdk"
7
+ version = "0.1.0"
8
+ description = "Python SDK for NodeLoom AI agent monitoring and telemetry"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "NodeLoom", email = "sdk@nodeloom.io" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Topic :: System :: Monitoring",
27
+ ]
28
+ keywords = ["nodeloom", "telemetry", "monitoring", "ai", "agents", "observability"]
29
+ dependencies = [
30
+ "requests>=2.28.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ langchain = ["langchain-core>=0.1.0"]
35
+ crewai = ["crewai>=0.1.0"]
36
+ dev = [
37
+ "pytest>=7.0",
38
+ "pytest-cov>=4.0",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://nodeloom.io"
43
+ Documentation = "https://docs.nodeloom.io/sdk/python"
44
+ Repository = "https://github.com/nodeloom/nodeloom-python-sdk"
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.setuptools.package-dir]
50
+ "" = "src"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ """NodeLoom Python SDK for AI agent monitoring and telemetry."""
2
+
3
+ from nodeloom.client import NodeLoomClient
4
+ from nodeloom.config import SDK_VERSION
5
+ from nodeloom.span import Span
6
+ from nodeloom.trace import Trace
7
+ from nodeloom.types import EventLevel, SpanType, TraceStatus
8
+
9
+ # Convenience alias
10
+ NodeLoom = NodeLoomClient
11
+
12
+ __version__ = SDK_VERSION
13
+
14
+ __all__ = [
15
+ "NodeLoom",
16
+ "NodeLoomClient",
17
+ "Trace",
18
+ "Span",
19
+ "SpanType",
20
+ "TraceStatus",
21
+ "EventLevel",
22
+ "__version__",
23
+ ]
@@ -0,0 +1,102 @@
1
+ """Background daemon thread that batches and sends telemetry events."""
2
+
3
+ import logging
4
+ import threading
5
+ from typing import Optional
6
+
7
+ from nodeloom.config import NodeLoomConfig
8
+ from nodeloom.queue import TelemetryQueue
9
+ from nodeloom.transport import HttpTransport
10
+
11
+ logger = logging.getLogger("nodeloom.batch_processor")
12
+
13
+
14
+ class BatchProcessor:
15
+ """Consumes events from a TelemetryQueue, batches them, and sends
16
+ them via HttpTransport.
17
+
18
+ Batches are flushed when either of the following conditions is met:
19
+ - The batch reaches ``config.batch_size`` events.
20
+ - ``config.flush_interval`` seconds have elapsed since the last flush.
21
+
22
+ The processor runs on a daemon thread, so it will not prevent the
23
+ interpreter from exiting. Call ``shutdown()`` to flush remaining
24
+ events before exit.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ config: NodeLoomConfig,
30
+ telemetry_queue: TelemetryQueue,
31
+ transport: HttpTransport,
32
+ ) -> None:
33
+ self._config = config
34
+ self._queue = telemetry_queue
35
+ self._transport = transport
36
+ self._shutdown_event = threading.Event()
37
+ self._flush_event = threading.Event()
38
+ self._thread: Optional[threading.Thread] = None
39
+
40
+ def start(self) -> None:
41
+ """Start the background processing thread."""
42
+ if self._thread is not None and self._thread.is_alive():
43
+ return
44
+ self._shutdown_event.clear()
45
+ self._thread = threading.Thread(
46
+ target=self._run,
47
+ name="nodeloom-batch-processor",
48
+ daemon=True,
49
+ )
50
+ self._thread.start()
51
+ logger.debug("BatchProcessor started")
52
+
53
+ def _run(self) -> None:
54
+ """Main loop for the background thread."""
55
+ while not self._shutdown_event.is_set():
56
+ # Wait for either the flush interval to elapse or a manual
57
+ # flush signal, whichever comes first.
58
+ self._flush_event.wait(timeout=self._config.flush_interval)
59
+ self._flush_event.clear()
60
+ self._flush_batch()
61
+
62
+ # Final flush on shutdown
63
+ self._flush_batch()
64
+ logger.debug("BatchProcessor stopped")
65
+
66
+ def _flush_batch(self) -> None:
67
+ """Drain events from the queue and send them in batches."""
68
+ while not self._queue.is_empty():
69
+ events = self._queue.drain(self._config.batch_size)
70
+ if not events:
71
+ break
72
+ try:
73
+ self._transport.send_batch(events)
74
+ except Exception:
75
+ logger.exception("Unexpected error while sending batch")
76
+
77
+ def flush(self) -> None:
78
+ """Request an immediate flush of pending events.
79
+
80
+ This signals the background thread to wake up and process
81
+ whatever is currently in the queue. It does not block until
82
+ the flush completes.
83
+ """
84
+ self._flush_event.set()
85
+
86
+ def shutdown(self, timeout: float = 10.0) -> None:
87
+ """Signal the processor to stop and wait for it to finish.
88
+
89
+ Args:
90
+ timeout: Maximum seconds to wait for the background thread
91
+ to complete its final flush.
92
+ """
93
+ logger.debug("BatchProcessor shutting down")
94
+ self._shutdown_event.set()
95
+ self._flush_event.set() # Wake the thread if it is sleeping
96
+ if self._thread is not None and self._thread.is_alive():
97
+ self._thread.join(timeout=timeout)
98
+ if self._thread.is_alive():
99
+ logger.warning(
100
+ "BatchProcessor thread did not terminate within %.1f seconds",
101
+ timeout,
102
+ )