uncoil-sdk 0.6.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.
- uncoil_sdk-0.6.0/PKG-INFO +231 -0
- uncoil_sdk-0.6.0/README.md +198 -0
- uncoil_sdk-0.6.0/pyproject.toml +45 -0
- uncoil_sdk-0.6.0/setup.cfg +4 -0
- uncoil_sdk-0.6.0/src/uncoil/__init__.py +876 -0
- uncoil_sdk-0.6.0/src/uncoil/__main__.py +6 -0
- uncoil_sdk-0.6.0/src/uncoil/auto.py +94 -0
- uncoil_sdk-0.6.0/src/uncoil/doctor.py +161 -0
- uncoil_sdk-0.6.0/src/uncoil/integrations.py +261 -0
- uncoil_sdk-0.6.0/src/uncoil/langgraph.py +51 -0
- uncoil_sdk-0.6.0/src/uncoil/tools.py +60 -0
- uncoil_sdk-0.6.0/src/uncoil_sdk.egg-info/PKG-INFO +231 -0
- uncoil_sdk-0.6.0/src/uncoil_sdk.egg-info/SOURCES.txt +21 -0
- uncoil_sdk-0.6.0/src/uncoil_sdk.egg-info/dependency_links.txt +1 -0
- uncoil_sdk-0.6.0/src/uncoil_sdk.egg-info/entry_points.txt +2 -0
- uncoil_sdk-0.6.0/src/uncoil_sdk.egg-info/requires.txt +21 -0
- uncoil_sdk-0.6.0/src/uncoil_sdk.egg-info/top_level.txt +1 -0
- uncoil_sdk-0.6.0/tests/test_auto.py +81 -0
- uncoil_sdk-0.6.0/tests/test_doctor.py +98 -0
- uncoil_sdk-0.6.0/tests/test_integrations.py +126 -0
- uncoil_sdk-0.6.0/tests/test_orchestrate.py +332 -0
- uncoil_sdk-0.6.0/tests/test_safety.py +169 -0
- uncoil_sdk-0.6.0/tests/test_steps.py +97 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: uncoil-sdk
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Observability, runtime enforcement, and tracing for AI agents.
|
|
5
|
+
Author-email: Uncoil Team <hello@uncoil.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.uncoil.dev
|
|
8
|
+
Project-URL: Documentation, https://www.uncoil.dev/docs
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/Uncoil-dev/Uncoil/issues
|
|
10
|
+
Project-URL: Source, https://github.com/Uncoil-dev/Uncoil
|
|
11
|
+
Keywords: ai,agents,observability,tracing,llm,llm-observability,agent-tracing,opentelemetry,cost-tracking,langchain,openai,anthropic
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.7
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: requests>=2.25.0
|
|
17
|
+
Requires-Dist: httpx>=0.24.0
|
|
18
|
+
Provides-Extra: openai
|
|
19
|
+
Requires-Dist: openai>=1.0; extra == "openai"
|
|
20
|
+
Provides-Extra: anthropic
|
|
21
|
+
Requires-Dist: anthropic>=0.25; extra == "anthropic"
|
|
22
|
+
Provides-Extra: langgraph
|
|
23
|
+
Requires-Dist: langchain-core>=0.2; extra == "langgraph"
|
|
24
|
+
Requires-Dist: langgraph>=0.2; extra == "langgraph"
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "test"
|
|
28
|
+
Requires-Dist: httpx2==2.13.1; extra == "test"
|
|
29
|
+
Requires-Dist: openai==3.31.0; extra == "test"
|
|
30
|
+
Requires-Dist: anthropic==1.14.0; extra == "test"
|
|
31
|
+
Requires-Dist: langgraph==1.2.15; extra == "test"
|
|
32
|
+
Requires-Dist: langchain-core==1.6.10; extra == "test"
|
|
33
|
+
|
|
34
|
+
# Uncoil Python SDK
|
|
35
|
+
|
|
36
|
+
Observability and tracing for AI agents. Gain full visibility into your agent's decision-making process, tool usage, and LLM costs.
|
|
37
|
+
|
|
38
|
+
Uncoil records each agent run as one trace: every model call, tool call, retry, failure and cost. It also ingests OpenTelemetry. Free plan available. Docs: [uncoil.dev/docs](https://www.uncoil.dev/docs). New to agent observability? Read the [complete guide](https://www.uncoil.dev/blog/ai-agent-observability-complete-guide).
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install uncoil-sdk
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
1. Get your API Key from the [Uncoil Dashboard](https://www.uncoil.dev).
|
|
49
|
+
2. Set it as an environment variable:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export UNCOIL_API_KEY=your_sk_live_...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
3. Instrument your agent:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from uncoil import trace_agent, llm_call, tool_call
|
|
59
|
+
|
|
60
|
+
@trace_agent(environment="development", release="quickstart")
|
|
61
|
+
async def my_agent(query: str):
|
|
62
|
+
# LLM Call
|
|
63
|
+
llm_call(
|
|
64
|
+
input_data=query,
|
|
65
|
+
output_data="Thinking...",
|
|
66
|
+
model_id="gpt-4o",
|
|
67
|
+
cost_usd=0.002,
|
|
68
|
+
latency_ms=800
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Tool Use
|
|
72
|
+
tool_call(
|
|
73
|
+
tool_name="web_search",
|
|
74
|
+
input_data={"q": query},
|
|
75
|
+
output_data="Found results...",
|
|
76
|
+
latency_ms=1200
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return "Final Answer"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Steps
|
|
83
|
+
|
|
84
|
+
Inside a `@trace_agent` function, every step you record is linked to the step it ran in, so the trace graph
|
|
85
|
+
shows which step called which and how loops repeat. Use `span` for a block and `observe` for a function:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import uncoil
|
|
89
|
+
|
|
90
|
+
@uncoil.observe(type="tool")
|
|
91
|
+
def search_docs(query):
|
|
92
|
+
return search(query)
|
|
93
|
+
|
|
94
|
+
@uncoil.trace_agent()
|
|
95
|
+
def answer(question):
|
|
96
|
+
for turn in range(5):
|
|
97
|
+
with uncoil.span("think", type="agent"):
|
|
98
|
+
with uncoil.span("chat", type="llm", input=question) as step:
|
|
99
|
+
reply = call_model(question)
|
|
100
|
+
step.set_output(reply.text)
|
|
101
|
+
step.set(model="claude-sonnet-5-5", input_tokens=reply.usage.input, output_tokens=reply.usage.output)
|
|
102
|
+
if reply.done:
|
|
103
|
+
return reply.text
|
|
104
|
+
search_docs(reply.query)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`type` is one of `agent`, `llm`, `tool`, `retrieval` or `span`. Errors are recorded with their type and
|
|
108
|
+
message. Concurrent runs (threads or asyncio tasks) stay in separate traces. Inputs and outputs have obvious
|
|
109
|
+
secrets (bearer tokens, API keys, JWTs, `password=` values) removed and are capped at 10,000 characters;
|
|
110
|
+
avoid sending anything else you would not want stored.
|
|
111
|
+
|
|
112
|
+
## Features
|
|
113
|
+
|
|
114
|
+
- **@trace_agent Decorator:** Automatically captures run lifecycle and errors.
|
|
115
|
+
- **Granular Spans:** Manually record LLMs, tools, and custom decisions.
|
|
116
|
+
- **Cost Tracking:** Monitor your infrastructure spend in real-time.
|
|
117
|
+
- **Asynchronous & Synchronous Support:** Works with `async/await` or standard functions.
|
|
118
|
+
- **Session context:** Attach `session_id`, `end_user_id`, `environment`, `release`, and `tags` to connect related traces.
|
|
119
|
+
|
|
120
|
+
## Feedback and evaluation scores
|
|
121
|
+
|
|
122
|
+
Pass `project_id` when constructing the client to attach end-user feedback and typed scores to traces:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
client = UncoilClient(project_id="your-project-id")
|
|
126
|
+
client.feedback(run_id, True, kind="thumb", comment="Helpful answer")
|
|
127
|
+
client.score(run_id, "answer_quality", 0.92, data_type="numeric")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Feedback and scores use the same API key as ingestion and do not interrupt the agent when delivery fails.
|
|
131
|
+
|
|
132
|
+
## Prompt deployment
|
|
133
|
+
|
|
134
|
+
Resolve a deployed prompt without shipping a new application build:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
deployed = client.get_prompt("support-agent", "production")
|
|
138
|
+
client.ingest(spanType="llm", input=question, output=answer, promptVersionId=deployed["version"]["id"])
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Prompt responses are cached for 60 seconds by default. Use `cache_ttl_seconds=0` or provide a `fallback` for development and guaranteed-availability behavior.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
146
|
+
|
|
147
|
+
## Provider integrations (0.4.0)
|
|
148
|
+
|
|
149
|
+
Install the optional provider/framework extras you use: `pip install 'uncoil-sdk[openai,anthropic,langgraph]'`.
|
|
150
|
+
Set `UNCOIL_API_KEY` and the existing regional configuration.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import uncoil
|
|
154
|
+
from openai import OpenAI, AsyncOpenAI
|
|
155
|
+
from anthropic import Anthropic
|
|
156
|
+
|
|
157
|
+
openai = uncoil.wrap_openai(OpenAI())
|
|
158
|
+
anthropic = uncoil.wrap_anthropic(Anthropic())
|
|
159
|
+
async_openai = uncoil.wrap_openai(AsyncOpenAI())
|
|
160
|
+
# Existing create calls keep their provider response types and exceptions.
|
|
161
|
+
reply = openai.responses.create(model=MODEL, input="Hello")
|
|
162
|
+
with anthropic.messages.stream(model=MODEL, max_tokens=100,
|
|
163
|
+
messages=[{"role": "user", "content": "Hello"}]) as stream:
|
|
164
|
+
for text in stream.text_stream:
|
|
165
|
+
print(text, end="")
|
|
166
|
+
|
|
167
|
+
# Add a callback to a compiled LangGraph invocation.
|
|
168
|
+
graph.invoke(inputs, config={"callbacks": [uncoil.langgraph_callback()]})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Wrap sync or async clients once. Supported: OpenAI chat completions and Responses `create` APIs,
|
|
172
|
+
including streams; Anthropic messages `create`/`stream` and beta equivalents, including sync/async
|
|
173
|
+
context managers, text streams and final messages. Close streams when stopping early. Raw response
|
|
174
|
+
resources and unsupported convenience methods are passed through without instrumentation.
|
|
175
|
+
|
|
176
|
+
Calls inside `trace_agent`/`span` inherit their run/parent and custom client. Standalone calls receive
|
|
177
|
+
an independent completed root. LangGraph callbacks preserve graph/node/tool/model parent IDs and
|
|
178
|
+
concurrent runs. Avoid using both framework model callbacks and a provider wrapper for the same call.
|
|
179
|
+
Provider packages remain optional; newer framework extras may require a newer Python than the core SDK.
|
|
180
|
+
|
|
181
|
+
Options: `client=custom_client`, `name="my-agent"`, `capture_input=False`, `capture_output=False`.
|
|
182
|
+
Existing redaction/size caps apply. Records include model, tokens, latency, TTFT, failures and raw usage
|
|
183
|
+
metadata. Costs default to zero with `costKnown=False`; supply
|
|
184
|
+
`calculate_cost=lambda usage: ...` with your own current provider rate card. The callback receives
|
|
185
|
+
`provider`, `model`, `input_tokens`, `output_tokens`, and raw `usage` (including cache details).
|
|
186
|
+
Missing usage is not estimated. Telemetry failures do not change provider results or exceptions.
|
|
187
|
+
|
|
188
|
+
Anthropic total input tokens include uncached input plus cache reads and cache writes; the original
|
|
189
|
+
usage breakdown remains in metadata for rate-card calculations. See the [provider’s token breakdown](https://platform.claude.com/docs/en/build-with-claude/prompt-caching).
|
|
190
|
+
|
|
191
|
+
## Framework one-liners
|
|
192
|
+
|
|
193
|
+
Quickest start: call `uncoil.init()` once at startup. Every OpenAI, Anthropic and LangChain / LangGraph call is traced, sync and async, with no client wrapping (`UNCOIL_API_KEY` is read from the environment). Libraries that are not installed are skipped, and `uncoil.uninit()` switches it off.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
import uncoil
|
|
197
|
+
uncoil.init()
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Calls made through a client you also wrapped with `wrap_openai` are recorded once, and model calls inside LangChain are recorded by the LangChain handler only. The Vercel AI SDK is JavaScript-only.
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
import uncoil
|
|
204
|
+
|
|
205
|
+
tools = uncoil.trace_tools({"search_docs": search_docs, "send_email": send_email}) # plain or async functions
|
|
206
|
+
client = uncoil.wrap_openai(OpenAI()) # also wrap_anthropic, langgraph_callback
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Copy-paste starters live in [`templates/`](./templates).
|
|
210
|
+
|
|
211
|
+
## Sampling, redaction and clearer errors
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
client = uncoil.UncoilClient(
|
|
215
|
+
sample_rate=0.1, # record about 1 in 10 runs; each run is kept or dropped whole
|
|
216
|
+
redact={"patterns": [re.compile(r"ACME-\d{6}")]}, # emails, phones and card numbers are masked by default; add your own
|
|
217
|
+
on_error=lambda error: log(str(error)), # problems come with a plain-language fix (error.hint)
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Spans that report an error are kept even when their run was not sampled (`keep_errors=False` turns that off). Secrets such as bearer tokens and API keys are always removed, and emails, phone numbers and card numbers are masked before anything leaves your app. To send them as they are, pass `redact={"pii": False}`.
|
|
222
|
+
|
|
223
|
+
## Setup checker
|
|
224
|
+
|
|
225
|
+
Not seeing traces? One command checks your key, region and network, then sends a test trace:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
uncoil-doctor # or: python -m uncoil doctor
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
It reads `UNCOIL_API_KEY` (and `UNCOIL_REGION` / `UNCOIL_HOST` if set), tells you in plain words what is wrong and how to fix it, and exits with code 1 if anything fails. Options: `--key`, `--region eu|us`, `--host`, `--no-send` (skip the test trace) and `--json`. The test trace is named "Uncoil setup check" and tagged `uncoil-doctor`; it counts as one trace on your plan.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Uncoil Python SDK
|
|
2
|
+
|
|
3
|
+
Observability and tracing for AI agents. Gain full visibility into your agent's decision-making process, tool usage, and LLM costs.
|
|
4
|
+
|
|
5
|
+
Uncoil records each agent run as one trace: every model call, tool call, retry, failure and cost. It also ingests OpenTelemetry. Free plan available. Docs: [uncoil.dev/docs](https://www.uncoil.dev/docs). New to agent observability? Read the [complete guide](https://www.uncoil.dev/blog/ai-agent-observability-complete-guide).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install uncoil-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
1. Get your API Key from the [Uncoil Dashboard](https://www.uncoil.dev).
|
|
16
|
+
2. Set it as an environment variable:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
export UNCOIL_API_KEY=your_sk_live_...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
3. Instrument your agent:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from uncoil import trace_agent, llm_call, tool_call
|
|
26
|
+
|
|
27
|
+
@trace_agent(environment="development", release="quickstart")
|
|
28
|
+
async def my_agent(query: str):
|
|
29
|
+
# LLM Call
|
|
30
|
+
llm_call(
|
|
31
|
+
input_data=query,
|
|
32
|
+
output_data="Thinking...",
|
|
33
|
+
model_id="gpt-4o",
|
|
34
|
+
cost_usd=0.002,
|
|
35
|
+
latency_ms=800
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Tool Use
|
|
39
|
+
tool_call(
|
|
40
|
+
tool_name="web_search",
|
|
41
|
+
input_data={"q": query},
|
|
42
|
+
output_data="Found results...",
|
|
43
|
+
latency_ms=1200
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return "Final Answer"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Steps
|
|
50
|
+
|
|
51
|
+
Inside a `@trace_agent` function, every step you record is linked to the step it ran in, so the trace graph
|
|
52
|
+
shows which step called which and how loops repeat. Use `span` for a block and `observe` for a function:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import uncoil
|
|
56
|
+
|
|
57
|
+
@uncoil.observe(type="tool")
|
|
58
|
+
def search_docs(query):
|
|
59
|
+
return search(query)
|
|
60
|
+
|
|
61
|
+
@uncoil.trace_agent()
|
|
62
|
+
def answer(question):
|
|
63
|
+
for turn in range(5):
|
|
64
|
+
with uncoil.span("think", type="agent"):
|
|
65
|
+
with uncoil.span("chat", type="llm", input=question) as step:
|
|
66
|
+
reply = call_model(question)
|
|
67
|
+
step.set_output(reply.text)
|
|
68
|
+
step.set(model="claude-sonnet-5-5", input_tokens=reply.usage.input, output_tokens=reply.usage.output)
|
|
69
|
+
if reply.done:
|
|
70
|
+
return reply.text
|
|
71
|
+
search_docs(reply.query)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`type` is one of `agent`, `llm`, `tool`, `retrieval` or `span`. Errors are recorded with their type and
|
|
75
|
+
message. Concurrent runs (threads or asyncio tasks) stay in separate traces. Inputs and outputs have obvious
|
|
76
|
+
secrets (bearer tokens, API keys, JWTs, `password=` values) removed and are capped at 10,000 characters;
|
|
77
|
+
avoid sending anything else you would not want stored.
|
|
78
|
+
|
|
79
|
+
## Features
|
|
80
|
+
|
|
81
|
+
- **@trace_agent Decorator:** Automatically captures run lifecycle and errors.
|
|
82
|
+
- **Granular Spans:** Manually record LLMs, tools, and custom decisions.
|
|
83
|
+
- **Cost Tracking:** Monitor your infrastructure spend in real-time.
|
|
84
|
+
- **Asynchronous & Synchronous Support:** Works with `async/await` or standard functions.
|
|
85
|
+
- **Session context:** Attach `session_id`, `end_user_id`, `environment`, `release`, and `tags` to connect related traces.
|
|
86
|
+
|
|
87
|
+
## Feedback and evaluation scores
|
|
88
|
+
|
|
89
|
+
Pass `project_id` when constructing the client to attach end-user feedback and typed scores to traces:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
client = UncoilClient(project_id="your-project-id")
|
|
93
|
+
client.feedback(run_id, True, kind="thumb", comment="Helpful answer")
|
|
94
|
+
client.score(run_id, "answer_quality", 0.92, data_type="numeric")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Feedback and scores use the same API key as ingestion and do not interrupt the agent when delivery fails.
|
|
98
|
+
|
|
99
|
+
## Prompt deployment
|
|
100
|
+
|
|
101
|
+
Resolve a deployed prompt without shipping a new application build:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
deployed = client.get_prompt("support-agent", "production")
|
|
105
|
+
client.ingest(spanType="llm", input=question, output=answer, promptVersionId=deployed["version"]["id"])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Prompt responses are cached for 60 seconds by default. Use `cache_ttl_seconds=0` or provide a `fallback` for development and guaranteed-availability behavior.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
113
|
+
|
|
114
|
+
## Provider integrations (0.4.0)
|
|
115
|
+
|
|
116
|
+
Install the optional provider/framework extras you use: `pip install 'uncoil-sdk[openai,anthropic,langgraph]'`.
|
|
117
|
+
Set `UNCOIL_API_KEY` and the existing regional configuration.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import uncoil
|
|
121
|
+
from openai import OpenAI, AsyncOpenAI
|
|
122
|
+
from anthropic import Anthropic
|
|
123
|
+
|
|
124
|
+
openai = uncoil.wrap_openai(OpenAI())
|
|
125
|
+
anthropic = uncoil.wrap_anthropic(Anthropic())
|
|
126
|
+
async_openai = uncoil.wrap_openai(AsyncOpenAI())
|
|
127
|
+
# Existing create calls keep their provider response types and exceptions.
|
|
128
|
+
reply = openai.responses.create(model=MODEL, input="Hello")
|
|
129
|
+
with anthropic.messages.stream(model=MODEL, max_tokens=100,
|
|
130
|
+
messages=[{"role": "user", "content": "Hello"}]) as stream:
|
|
131
|
+
for text in stream.text_stream:
|
|
132
|
+
print(text, end="")
|
|
133
|
+
|
|
134
|
+
# Add a callback to a compiled LangGraph invocation.
|
|
135
|
+
graph.invoke(inputs, config={"callbacks": [uncoil.langgraph_callback()]})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Wrap sync or async clients once. Supported: OpenAI chat completions and Responses `create` APIs,
|
|
139
|
+
including streams; Anthropic messages `create`/`stream` and beta equivalents, including sync/async
|
|
140
|
+
context managers, text streams and final messages. Close streams when stopping early. Raw response
|
|
141
|
+
resources and unsupported convenience methods are passed through without instrumentation.
|
|
142
|
+
|
|
143
|
+
Calls inside `trace_agent`/`span` inherit their run/parent and custom client. Standalone calls receive
|
|
144
|
+
an independent completed root. LangGraph callbacks preserve graph/node/tool/model parent IDs and
|
|
145
|
+
concurrent runs. Avoid using both framework model callbacks and a provider wrapper for the same call.
|
|
146
|
+
Provider packages remain optional; newer framework extras may require a newer Python than the core SDK.
|
|
147
|
+
|
|
148
|
+
Options: `client=custom_client`, `name="my-agent"`, `capture_input=False`, `capture_output=False`.
|
|
149
|
+
Existing redaction/size caps apply. Records include model, tokens, latency, TTFT, failures and raw usage
|
|
150
|
+
metadata. Costs default to zero with `costKnown=False`; supply
|
|
151
|
+
`calculate_cost=lambda usage: ...` with your own current provider rate card. The callback receives
|
|
152
|
+
`provider`, `model`, `input_tokens`, `output_tokens`, and raw `usage` (including cache details).
|
|
153
|
+
Missing usage is not estimated. Telemetry failures do not change provider results or exceptions.
|
|
154
|
+
|
|
155
|
+
Anthropic total input tokens include uncached input plus cache reads and cache writes; the original
|
|
156
|
+
usage breakdown remains in metadata for rate-card calculations. See the [provider’s token breakdown](https://platform.claude.com/docs/en/build-with-claude/prompt-caching).
|
|
157
|
+
|
|
158
|
+
## Framework one-liners
|
|
159
|
+
|
|
160
|
+
Quickest start: call `uncoil.init()` once at startup. Every OpenAI, Anthropic and LangChain / LangGraph call is traced, sync and async, with no client wrapping (`UNCOIL_API_KEY` is read from the environment). Libraries that are not installed are skipped, and `uncoil.uninit()` switches it off.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
import uncoil
|
|
164
|
+
uncoil.init()
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Calls made through a client you also wrapped with `wrap_openai` are recorded once, and model calls inside LangChain are recorded by the LangChain handler only. The Vercel AI SDK is JavaScript-only.
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
import uncoil
|
|
171
|
+
|
|
172
|
+
tools = uncoil.trace_tools({"search_docs": search_docs, "send_email": send_email}) # plain or async functions
|
|
173
|
+
client = uncoil.wrap_openai(OpenAI()) # also wrap_anthropic, langgraph_callback
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Copy-paste starters live in [`templates/`](./templates).
|
|
177
|
+
|
|
178
|
+
## Sampling, redaction and clearer errors
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
client = uncoil.UncoilClient(
|
|
182
|
+
sample_rate=0.1, # record about 1 in 10 runs; each run is kept or dropped whole
|
|
183
|
+
redact={"patterns": [re.compile(r"ACME-\d{6}")]}, # emails, phones and card numbers are masked by default; add your own
|
|
184
|
+
on_error=lambda error: log(str(error)), # problems come with a plain-language fix (error.hint)
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Spans that report an error are kept even when their run was not sampled (`keep_errors=False` turns that off). Secrets such as bearer tokens and API keys are always removed, and emails, phone numbers and card numbers are masked before anything leaves your app. To send them as they are, pass `redact={"pii": False}`.
|
|
189
|
+
|
|
190
|
+
## Setup checker
|
|
191
|
+
|
|
192
|
+
Not seeing traces? One command checks your key, region and network, then sends a test trace:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
uncoil-doctor # or: python -m uncoil doctor
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
It reads `UNCOIL_API_KEY` (and `UNCOIL_REGION` / `UNCOIL_HOST` if set), tells you in plain words what is wrong and how to fix it, and exits with code 1 if anything fails. Options: `--key`, `--region eu|us`, `--host`, `--no-send` (skip the test trace) and `--json`. The test trace is named "Uncoil setup check" and tagged `uncoil-doctor`; it counts as one trace on your plan.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "uncoil-sdk"
|
|
7
|
+
version = "0.6.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Uncoil Team", email="hello@uncoil.dev" },
|
|
10
|
+
]
|
|
11
|
+
description = "Observability, runtime enforcement, and tracing for AI agents."
|
|
12
|
+
license = "MIT"
|
|
13
|
+
keywords = ["ai", "agents", "observability", "tracing", "llm", "llm-observability", "agent-tracing", "opentelemetry", "cost-tracking", "langchain", "openai", "anthropic"]
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
requires-python = ">=3.7"
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"requests>=2.25.0",
|
|
22
|
+
"httpx>=0.24.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
uncoil-doctor = "uncoil.doctor:main"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
"Homepage" = "https://www.uncoil.dev"
|
|
33
|
+
"Documentation" = "https://www.uncoil.dev/docs"
|
|
34
|
+
"Bug Tracker" = "https://github.com/Uncoil-dev/Uncoil/issues"
|
|
35
|
+
"Source" = "https://github.com/Uncoil-dev/Uncoil"
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
pythonpath = ["src"]
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
openai = ["openai>=1.0"]
|
|
43
|
+
anthropic = ["anthropic>=0.25"]
|
|
44
|
+
langgraph = ["langchain-core>=0.2", "langgraph>=0.2"]
|
|
45
|
+
test = ["pytest>=8", "pytest-asyncio>=0.24", "httpx2==2.13.1", "openai==3.31.0", "anthropic==1.14.0", "langgraph==1.2.15", "langchain-core==1.6.10"]
|