metalins 0.4.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.
- metalins-0.4.0/.gitignore +19 -0
- metalins-0.4.0/LICENSE +2 -0
- metalins-0.4.0/PKG-INFO +142 -0
- metalins-0.4.0/README.md +111 -0
- metalins-0.4.0/docs/integrations/anthropic.md +257 -0
- metalins-0.4.0/docs/integrations/fastapi.md +202 -0
- metalins-0.4.0/docs/integrations/langchain.md +157 -0
- metalins-0.4.0/metalins/__init__.py +57 -0
- metalins-0.4.0/metalins/agent.py +219 -0
- metalins-0.4.0/metalins/client.py +262 -0
- metalins-0.4.0/metalins/errors.py +18 -0
- metalins-0.4.0/metalins/integrations/__init__.py +11 -0
- metalins-0.4.0/metalins/integrations/anthropic.py +366 -0
- metalins-0.4.0/metalins/integrations/fastapi.py +245 -0
- metalins-0.4.0/metalins/integrations/langchain.py +143 -0
- metalins-0.4.0/metalins/mcp_session.py +335 -0
- metalins-0.4.0/metalins/state.py +95 -0
- metalins-0.4.0/metalins/worker.py +94 -0
- metalins-0.4.0/pyproject.toml +45 -0
- metalins-0.4.0/tests/__init__.py +0 -0
- metalins-0.4.0/tests/test_agent.py +338 -0
- metalins-0.4.0/tests/test_anthropic.py +439 -0
- metalins-0.4.0/tests/test_client.py +185 -0
- metalins-0.4.0/tests/test_fastapi.py +412 -0
- metalins-0.4.0/tests/test_mcp_session.py +176 -0
metalins-0.4.0/LICENSE
ADDED
metalins-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: metalins
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: SDK for Metalins — identity verification for AI agents.
|
|
5
|
+
Project-URL: Homepage, https://metalins.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/Metalins/metalins-python-sdk
|
|
7
|
+
Author-email: Metalins <support@metalins.ai>
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,ai,identity,metalins,verification
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: httpx>=0.27.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: starlette>=0.37.0; extra == 'dev'
|
|
26
|
+
Provides-Extra: fastapi
|
|
27
|
+
Requires-Dist: fastapi>=0.110.0; extra == 'fastapi'
|
|
28
|
+
Provides-Extra: langchain
|
|
29
|
+
Requires-Dist: langchain-core>=0.3.0; extra == 'langchain'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# metalins
|
|
33
|
+
|
|
34
|
+
**Zero Trust identity verification for AI agents.**
|
|
35
|
+
|
|
36
|
+
Your agents in production are black boxes. Metalins verifies they're still the same agents you deployed — same model, same behavior, continuously. It's the behavioral verification layer in the Zero Trust stack for AI agents.
|
|
37
|
+
|
|
38
|
+
## How it works
|
|
39
|
+
|
|
40
|
+
1. The SDK hashes your agent's inputs and outputs **locally** — raw prompts and responses never leave your infrastructure.
|
|
41
|
+
2. Signed hashes are sent to `api.metalins.ai`, where the behavioral engine runs.
|
|
42
|
+
3. The engine returns a continuous verification status: `verified`, `caution`, or `not_verified`.
|
|
43
|
+
|
|
44
|
+
Your data stays in your infra. We only see fingerprints.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install metalins
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
Three lines to start verifying your agent:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import metalins
|
|
58
|
+
|
|
59
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-agent")
|
|
60
|
+
agent.start()
|
|
61
|
+
|
|
62
|
+
# Log each turn — hashing happens locally, automatically
|
|
63
|
+
agent.log(input=user_message, output=agent_reply)
|
|
64
|
+
|
|
65
|
+
# Check verification status at any time
|
|
66
|
+
status = agent.get_status() # "verified" | "caution" | "not_verified"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or as a context manager:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
with metalins.Agent(api_key="ml_live_...", name="my-agent") as agent:
|
|
73
|
+
agent.log(input=user_message, output=agent_reply)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Get your API key at [metalins.ai](https://metalins.ai).
|
|
77
|
+
|
|
78
|
+
## Integrations
|
|
79
|
+
|
|
80
|
+
### LangChain
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from metalins import Agent
|
|
84
|
+
from metalins.integrations.langchain import MetalinsCallbackHandler
|
|
85
|
+
|
|
86
|
+
agent = Agent(api_key="ml_live_...", name="my-bot").start()
|
|
87
|
+
handler = MetalinsCallbackHandler(agent)
|
|
88
|
+
|
|
89
|
+
chain.invoke(user_input, config={"callbacks": [handler]})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Every chain and LLM call is logged automatically — no manual `agent.log()` needed.
|
|
93
|
+
|
|
94
|
+
### FastAPI / Starlette
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
import metalins
|
|
98
|
+
from metalins.integrations.fastapi import MetalinsMiddleware
|
|
99
|
+
|
|
100
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-api").start()
|
|
101
|
+
app.add_middleware(MetalinsMiddleware, agent=agent)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Every request/response pair is logged automatically. Bodies are hashed locally and never buffered in full (1 MiB cap by default). Skip noisy endpoints with `exclude_paths=["/health"]`.
|
|
105
|
+
|
|
106
|
+
### Anthropic SDK
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import metalins
|
|
110
|
+
|
|
111
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-claude-agent").start()
|
|
112
|
+
|
|
113
|
+
with metalins.trace(agent):
|
|
114
|
+
response = client.messages.create(...)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Or use the `@metalins.monitor` decorator on any function that calls the Anthropic SDK.
|
|
118
|
+
|
|
119
|
+
## What leaves your infrastructure
|
|
120
|
+
|
|
121
|
+
Only hashed fingerprints — never raw text:
|
|
122
|
+
|
|
123
|
+
| What we receive | What stays with you |
|
|
124
|
+
|-----------------|---------------------|
|
|
125
|
+
| SHA-256 hash of input | Raw prompt text |
|
|
126
|
+
| SHA-256 hash of output | Raw response text |
|
|
127
|
+
| Timestamp + agent ID | Your users' data |
|
|
128
|
+
| HMAC-signed event chain | Your model config |
|
|
129
|
+
|
|
130
|
+
The behavioral engine compares fingerprint patterns over time. It does not reconstruct your prompts or responses.
|
|
131
|
+
|
|
132
|
+
## State persistence
|
|
133
|
+
|
|
134
|
+
The SDK persists the agent session (ID, secret, hash chain) to `~/.metalins/<name>.json` with `0600` permissions by default. To store it elsewhere — a database, a secrets manager — pass any object with `load()` and `save()`:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-bot", store=my_store)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
Apache 2.0. See [LICENSE](LICENSE).
|
metalins-0.4.0/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# metalins
|
|
2
|
+
|
|
3
|
+
**Zero Trust identity verification for AI agents.**
|
|
4
|
+
|
|
5
|
+
Your agents in production are black boxes. Metalins verifies they're still the same agents you deployed — same model, same behavior, continuously. It's the behavioral verification layer in the Zero Trust stack for AI agents.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
1. The SDK hashes your agent's inputs and outputs **locally** — raw prompts and responses never leave your infrastructure.
|
|
10
|
+
2. Signed hashes are sent to `api.metalins.ai`, where the behavioral engine runs.
|
|
11
|
+
3. The engine returns a continuous verification status: `verified`, `caution`, or `not_verified`.
|
|
12
|
+
|
|
13
|
+
Your data stays in your infra. We only see fingerprints.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install metalins
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
Three lines to start verifying your agent:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import metalins
|
|
27
|
+
|
|
28
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-agent")
|
|
29
|
+
agent.start()
|
|
30
|
+
|
|
31
|
+
# Log each turn — hashing happens locally, automatically
|
|
32
|
+
agent.log(input=user_message, output=agent_reply)
|
|
33
|
+
|
|
34
|
+
# Check verification status at any time
|
|
35
|
+
status = agent.get_status() # "verified" | "caution" | "not_verified"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or as a context manager:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
with metalins.Agent(api_key="ml_live_...", name="my-agent") as agent:
|
|
42
|
+
agent.log(input=user_message, output=agent_reply)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Get your API key at [metalins.ai](https://metalins.ai).
|
|
46
|
+
|
|
47
|
+
## Integrations
|
|
48
|
+
|
|
49
|
+
### LangChain
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from metalins import Agent
|
|
53
|
+
from metalins.integrations.langchain import MetalinsCallbackHandler
|
|
54
|
+
|
|
55
|
+
agent = Agent(api_key="ml_live_...", name="my-bot").start()
|
|
56
|
+
handler = MetalinsCallbackHandler(agent)
|
|
57
|
+
|
|
58
|
+
chain.invoke(user_input, config={"callbacks": [handler]})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Every chain and LLM call is logged automatically — no manual `agent.log()` needed.
|
|
62
|
+
|
|
63
|
+
### FastAPI / Starlette
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import metalins
|
|
67
|
+
from metalins.integrations.fastapi import MetalinsMiddleware
|
|
68
|
+
|
|
69
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-api").start()
|
|
70
|
+
app.add_middleware(MetalinsMiddleware, agent=agent)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Every request/response pair is logged automatically. Bodies are hashed locally and never buffered in full (1 MiB cap by default). Skip noisy endpoints with `exclude_paths=["/health"]`.
|
|
74
|
+
|
|
75
|
+
### Anthropic SDK
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import metalins
|
|
79
|
+
|
|
80
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-claude-agent").start()
|
|
81
|
+
|
|
82
|
+
with metalins.trace(agent):
|
|
83
|
+
response = client.messages.create(...)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Or use the `@metalins.monitor` decorator on any function that calls the Anthropic SDK.
|
|
87
|
+
|
|
88
|
+
## What leaves your infrastructure
|
|
89
|
+
|
|
90
|
+
Only hashed fingerprints — never raw text:
|
|
91
|
+
|
|
92
|
+
| What we receive | What stays with you |
|
|
93
|
+
|-----------------|---------------------|
|
|
94
|
+
| SHA-256 hash of input | Raw prompt text |
|
|
95
|
+
| SHA-256 hash of output | Raw response text |
|
|
96
|
+
| Timestamp + agent ID | Your users' data |
|
|
97
|
+
| HMAC-signed event chain | Your model config |
|
|
98
|
+
|
|
99
|
+
The behavioral engine compares fingerprint patterns over time. It does not reconstruct your prompts or responses.
|
|
100
|
+
|
|
101
|
+
## State persistence
|
|
102
|
+
|
|
103
|
+
The SDK persists the agent session (ID, secret, hash chain) to `~/.metalins/<name>.json` with `0600` permissions by default. To store it elsewhere — a database, a secrets manager — pass any object with `load()` and `save()`:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-bot", store=my_store)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
Apache 2.0. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Anthropic SDK Integration — metalins.trace and @metalins.monitor
|
|
2
|
+
|
|
3
|
+
The Anthropic integration provides two primitives for recording Metalins
|
|
4
|
+
verification events when calling the Anthropic SDK directly (no LangChain
|
|
5
|
+
required):
|
|
6
|
+
|
|
7
|
+
- **`metalins.trace(agent)`** — a context manager that wraps any block
|
|
8
|
+
containing Anthropic API calls.
|
|
9
|
+
- **`@metalins.monitor(agent)`** — a decorator that wraps a function whose
|
|
10
|
+
first argument is the messages input and whose return value is the output.
|
|
11
|
+
|
|
12
|
+
Both work for sync and async code, capture input/output, and call
|
|
13
|
+
`agent.log(...)` exactly once per turn.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install metalins anthropic
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
No extra `metalins` optional-dependency is needed — the Anthropic integration
|
|
22
|
+
module imports nothing from the `anthropic` package at load time. It works by
|
|
23
|
+
duck-typing the response object, so it is compatible with any version of the
|
|
24
|
+
Anthropic SDK.
|
|
25
|
+
|
|
26
|
+
## Context Manager — `metalins.trace`
|
|
27
|
+
|
|
28
|
+
### Sync Usage
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import anthropic
|
|
32
|
+
import metalins
|
|
33
|
+
|
|
34
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-claude-agent").start()
|
|
35
|
+
client = anthropic.Anthropic()
|
|
36
|
+
|
|
37
|
+
messages = [{"role": "user", "content": "Summarise this article: ..."}]
|
|
38
|
+
|
|
39
|
+
with metalins.trace(agent) as t:
|
|
40
|
+
t.set_input(messages) # capture what you're sending
|
|
41
|
+
response = client.messages.create(
|
|
42
|
+
model="claude-opus-4-5",
|
|
43
|
+
max_tokens=1024,
|
|
44
|
+
messages=messages,
|
|
45
|
+
)
|
|
46
|
+
t.set_output(response) # capture the Anthropic response
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Async Usage
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import anthropic
|
|
53
|
+
import metalins
|
|
54
|
+
|
|
55
|
+
agent = metalins.Agent(api_key="ml_live_...", name="my-async-agent").start()
|
|
56
|
+
async_client = anthropic.AsyncAnthropic()
|
|
57
|
+
|
|
58
|
+
async def handle_request(messages: list[dict]) -> str:
|
|
59
|
+
async with metalins.trace(agent) as t:
|
|
60
|
+
t.set_input(messages)
|
|
61
|
+
response = await async_client.messages.create(
|
|
62
|
+
model="claude-haiku-4-5",
|
|
63
|
+
max_tokens=512,
|
|
64
|
+
messages=messages,
|
|
65
|
+
)
|
|
66
|
+
t.set_output(response)
|
|
67
|
+
return response.content[0].text
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### How the Context Manager Works
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
with metalins.trace(agent) as t:
|
|
74
|
+
...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`t` is a `TraceContext` object with two methods:
|
|
78
|
+
|
|
79
|
+
| Method | Purpose |
|
|
80
|
+
|--------|---------|
|
|
81
|
+
| `t.set_input(value)` | Record the input for this turn. Accepts a list of message dicts, a string, bytes, or any JSON-serialisable object. |
|
|
82
|
+
| `t.set_output(value)` | Record the output. Accepts an Anthropic `Message`, a plain string, or any JSON-serialisable object. Text is automatically extracted from `response.content[0].text`. |
|
|
83
|
+
|
|
84
|
+
If the block raises an exception, **nothing is logged** — the exception
|
|
85
|
+
propagates normally and the turn is treated as failed.
|
|
86
|
+
|
|
87
|
+
## Decorator — `@metalins.monitor`
|
|
88
|
+
|
|
89
|
+
Use `monitor` when you have a function dedicated to calling Claude. The first
|
|
90
|
+
positional argument is captured as the input and the return value as the output.
|
|
91
|
+
|
|
92
|
+
### Sync Decorator
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import anthropic
|
|
96
|
+
import metalins
|
|
97
|
+
|
|
98
|
+
agent = metalins.Agent(api_key="ml_live_...", name="classification-agent").start()
|
|
99
|
+
client = anthropic.Anthropic()
|
|
100
|
+
|
|
101
|
+
@metalins.monitor(agent)
|
|
102
|
+
def classify_ticket(messages: list[dict]) -> str:
|
|
103
|
+
response = client.messages.create(
|
|
104
|
+
model="claude-haiku-4-5",
|
|
105
|
+
max_tokens=256,
|
|
106
|
+
messages=messages,
|
|
107
|
+
)
|
|
108
|
+
return response.content[0].text
|
|
109
|
+
|
|
110
|
+
# Call normally — Metalins logs input + output automatically.
|
|
111
|
+
result = classify_ticket([{"role": "user", "content": "My order never arrived."}])
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Async Decorator
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import anthropic
|
|
118
|
+
import metalins
|
|
119
|
+
|
|
120
|
+
agent = metalins.Agent(api_key="ml_live_...", name="refund-agent").start()
|
|
121
|
+
async_client = anthropic.AsyncAnthropic()
|
|
122
|
+
|
|
123
|
+
@metalins.monitor(agent)
|
|
124
|
+
async def process_refund(messages: list[dict]) -> str:
|
|
125
|
+
response = await async_client.messages.create(
|
|
126
|
+
model="claude-opus-4-5",
|
|
127
|
+
max_tokens=1024,
|
|
128
|
+
messages=messages,
|
|
129
|
+
)
|
|
130
|
+
return response.content[0].text
|
|
131
|
+
|
|
132
|
+
# In your async route / task:
|
|
133
|
+
reply = await process_refund([{"role": "user", "content": "Refund order #42"}])
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### What `monitor` Captures
|
|
137
|
+
|
|
138
|
+
| Logged field | Source |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `input` | The first positional argument (JSON-serialised if it's a list/dict) |
|
|
141
|
+
| `output` | The return value — text extracted if it's an Anthropic Message, otherwise JSON-serialised |
|
|
142
|
+
|
|
143
|
+
If the function raises, **nothing is logged**.
|
|
144
|
+
|
|
145
|
+
## Configuration Options
|
|
146
|
+
|
|
147
|
+
Both `trace` and `monitor` accept the same keyword arguments:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
metalins.trace(
|
|
151
|
+
agent,
|
|
152
|
+
|
|
153
|
+
# Extra key/values attached to the Metalins event.
|
|
154
|
+
metadata={"service": "refund-agent", "env": "prod"},
|
|
155
|
+
|
|
156
|
+
# Re-raise agent.log() failures. Default False — log failures are
|
|
157
|
+
# silenced so a Metalins outage never breaks your application.
|
|
158
|
+
raise_on_error=False,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@metalins.monitor(
|
|
162
|
+
agent,
|
|
163
|
+
metadata={"model": "claude-haiku-4-5"},
|
|
164
|
+
raise_on_error=False,
|
|
165
|
+
)
|
|
166
|
+
async def run(messages): ...
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Choosing Between `trace` and `monitor`
|
|
170
|
+
|
|
171
|
+
| Scenario | Recommended primitive |
|
|
172
|
+
|---|---|
|
|
173
|
+
| Ad-hoc calls spread across a function body | `metalins.trace` context manager |
|
|
174
|
+
| A dedicated function that calls Claude and returns its result | `@metalins.monitor` decorator |
|
|
175
|
+
| Async FastAPI route that calls Claude directly | Either — `trace` gives more granular control |
|
|
176
|
+
| Multi-step reasoning where you want one event per turn | `metalins.trace` — call `set_input`/`set_output` once per conversation turn |
|
|
177
|
+
|
|
178
|
+
## Full Example — FastAPI Route with Async Anthropic
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
import anthropic
|
|
182
|
+
import metalins
|
|
183
|
+
from fastapi import FastAPI
|
|
184
|
+
from pydantic import BaseModel
|
|
185
|
+
from contextlib import asynccontextmanager
|
|
186
|
+
|
|
187
|
+
metalins_agent = metalins.Agent(api_key="ml_live_...", name="support-bot")
|
|
188
|
+
anthropic_client = anthropic.AsyncAnthropic()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@asynccontextmanager
|
|
192
|
+
async def lifespan(app):
|
|
193
|
+
metalins_agent.start()
|
|
194
|
+
yield
|
|
195
|
+
metalins_agent.stop()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
app = FastAPI(lifespan=lifespan)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class ChatRequest(BaseModel):
|
|
202
|
+
messages: list[dict]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.post("/chat")
|
|
206
|
+
async def chat(req: ChatRequest):
|
|
207
|
+
async with metalins.trace(metalins_agent) as t:
|
|
208
|
+
t.set_input(req.messages)
|
|
209
|
+
response = await anthropic_client.messages.create(
|
|
210
|
+
model="claude-haiku-4-5",
|
|
211
|
+
max_tokens=1024,
|
|
212
|
+
messages=req.messages,
|
|
213
|
+
)
|
|
214
|
+
t.set_output(response)
|
|
215
|
+
return {"reply": response.content[0].text}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Full Example — Decorator Pattern with Metadata
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
import anthropic
|
|
222
|
+
import metalins
|
|
223
|
+
|
|
224
|
+
agent = metalins.Agent(api_key="ml_live_...", name="email-triage").start()
|
|
225
|
+
client = anthropic.Anthropic()
|
|
226
|
+
|
|
227
|
+
SYSTEM_PROMPT = "Classify the following support email into: billing, technical, general."
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@metalins.monitor(agent, metadata={"pipeline": "email-triage", "model": "claude-haiku-4-5"})
|
|
231
|
+
def triage_email(messages: list[dict]) -> str:
|
|
232
|
+
response = client.messages.create(
|
|
233
|
+
model="claude-haiku-4-5",
|
|
234
|
+
max_tokens=128,
|
|
235
|
+
system=SYSTEM_PROMPT,
|
|
236
|
+
messages=messages,
|
|
237
|
+
)
|
|
238
|
+
return response.content[0].text
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
category = triage_email([{"role": "user", "content": "I was charged twice this month."}])
|
|
242
|
+
print(category) # "billing"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Error Handling
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
# Default: swallow log errors (recommended for production)
|
|
249
|
+
with metalins.trace(agent) as t:
|
|
250
|
+
... # agent.log() failure is silenced — your app keeps working
|
|
251
|
+
|
|
252
|
+
# Opt in to raising: useful during local development
|
|
253
|
+
with metalins.trace(agent, raise_on_error=True) as t:
|
|
254
|
+
... # will raise if agent.log() fails
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The same option is available on `@metalins.monitor(agent, raise_on_error=True)`.
|