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.
- nodeloom_sdk-0.1.0/PKG-INFO +207 -0
- nodeloom_sdk-0.1.0/README.md +175 -0
- nodeloom_sdk-0.1.0/pyproject.toml +50 -0
- nodeloom_sdk-0.1.0/setup.cfg +4 -0
- nodeloom_sdk-0.1.0/src/nodeloom/__init__.py +23 -0
- nodeloom_sdk-0.1.0/src/nodeloom/batch_processor.py +102 -0
- nodeloom_sdk-0.1.0/src/nodeloom/client.py +159 -0
- nodeloom_sdk-0.1.0/src/nodeloom/config.py +56 -0
- nodeloom_sdk-0.1.0/src/nodeloom/integrations/__init__.py +1 -0
- nodeloom_sdk-0.1.0/src/nodeloom/integrations/crewai.py +190 -0
- nodeloom_sdk-0.1.0/src/nodeloom/integrations/langchain.py +410 -0
- nodeloom_sdk-0.1.0/src/nodeloom/queue.py +64 -0
- nodeloom_sdk-0.1.0/src/nodeloom/span.py +170 -0
- nodeloom_sdk-0.1.0/src/nodeloom/trace.py +187 -0
- nodeloom_sdk-0.1.0/src/nodeloom/transport.py +126 -0
- nodeloom_sdk-0.1.0/src/nodeloom/types.py +29 -0
- nodeloom_sdk-0.1.0/src/nodeloom_sdk.egg-info/PKG-INFO +207 -0
- nodeloom_sdk-0.1.0/src/nodeloom_sdk.egg-info/SOURCES.txt +23 -0
- nodeloom_sdk-0.1.0/src/nodeloom_sdk.egg-info/dependency_links.txt +1 -0
- nodeloom_sdk-0.1.0/src/nodeloom_sdk.egg-info/requires.txt +11 -0
- nodeloom_sdk-0.1.0/src/nodeloom_sdk.egg-info/top_level.txt +1 -0
- nodeloom_sdk-0.1.0/tests/test_batch_processor.py +481 -0
- nodeloom_sdk-0.1.0/tests/test_client.py +261 -0
- nodeloom_sdk-0.1.0/tests/test_langchain.py +600 -0
- nodeloom_sdk-0.1.0/tests/test_trace.py +436 -0
|
@@ -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,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
|
+
)
|