@moda-ai/cli 1.11.0 → 1.12.2
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.
- package/dist/cli.js +5 -0
- package/dist/harness-remote.d.ts +33 -0
- package/dist/harness-remote.js +203 -0
- package/dist/harness.d.ts +75 -0
- package/dist/harness.js +92 -37
- package/dist/init/index.js +14 -4
- package/dist/init/sdk-guide.d.ts +28 -1
- package/dist/init/sdk-guide.js +102 -98
- package/dist/init/sdk-integration.d.ts +23 -3
- package/dist/init/sdk-integration.js +106 -24
- package/dist/init/skill-resolver.d.ts +19 -0
- package/dist/init/skill-resolver.js +78 -0
- package/dist/skills-bundled.d.ts +35 -0
- package/dist/skills-bundled.js +157 -0
- package/dist/skills-manifest.d.ts +33 -0
- package/dist/skills-manifest.js +244 -0
- package/dist/skills.js +22 -142
- package/package.json +6 -4
- package/skills/integration/index.json +123 -0
- package/skills/integration/integration-node-anthropic/EXAMPLE.md +59 -0
- package/skills/integration/integration-node-anthropic/SKILL.md +150 -0
- package/skills/integration/integration-node-generic/EXAMPLE.md +77 -0
- package/skills/integration/integration-node-generic/SKILL.md +170 -0
- package/skills/integration/integration-node-langchain/EXAMPLE.md +59 -0
- package/skills/integration/integration-node-langchain/SKILL.md +144 -0
- package/skills/integration/integration-node-openai/EXAMPLE.md +55 -0
- package/skills/integration/integration-node-openai/SKILL.md +148 -0
- package/skills/integration/integration-node-vercel-ai-sdk/EXAMPLE.md +55 -0
- package/skills/integration/integration-node-vercel-ai-sdk/SKILL.md +179 -0
- package/skills/integration/integration-node-vercel-ai-sdk/references/prompt-attribution.md +31 -0
- package/skills/integration/integration-python-anthropic/EXAMPLE.md +66 -0
- package/skills/integration/integration-python-anthropic/SKILL.md +153 -0
- package/skills/integration/integration-python-claude-agent-sdk/EXAMPLE.md +61 -0
- package/skills/integration/integration-python-claude-agent-sdk/SKILL.md +129 -0
- package/skills/integration/integration-python-generic/EXAMPLE.md +46 -0
- package/skills/integration/integration-python-generic/SKILL.md +146 -0
- package/skills/integration/integration-python-langchain/EXAMPLE.md +63 -0
- package/skills/integration/integration-python-langchain/SKILL.md +141 -0
- package/skills/integration/integration-python-openai/EXAMPLE.md +65 -0
- package/skills/integration/integration-python-openai/SKILL.md +151 -0
- package/dist/init/detect.d.ts +0 -9
- package/dist/init/detect.js +0 -179
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-python-anthropic
|
|
3
|
+
name: "Integrate Moda with the Anthropic SDK (Python)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: python
|
|
8
|
+
frameworks: []
|
|
9
|
+
providers:
|
|
10
|
+
- anthropic
|
|
11
|
+
sdk_package: moda-ai
|
|
12
|
+
summary: "Auto-instrument anthropic client calls with Moda in a Python app; no per-call changes required."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with the Anthropic SDK (Python)
|
|
16
|
+
|
|
17
|
+
Follow the steps below in order. Each step has a stop condition; when you hit
|
|
18
|
+
one, stop and report instead of guessing. `EXAMPLE.md` in this directory shows
|
|
19
|
+
the complete finished integration; match its structure. For API details beyond
|
|
20
|
+
this skill, read https://docs.moda.dev/ingestion/providers/anthropic and
|
|
21
|
+
https://docs.moda.dev/ingestion/moda-sdk.
|
|
22
|
+
|
|
23
|
+
## Safety rules (apply to every step)
|
|
24
|
+
|
|
25
|
+
- Only touch the repository you were launched in.
|
|
26
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
27
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
28
|
+
environment or a secret manager.
|
|
29
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
30
|
+
- Do not commit unless the user asks.
|
|
31
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
32
|
+
guessing.
|
|
33
|
+
|
|
34
|
+
## Step 1: Confirm the raw Anthropic client is the integration surface
|
|
35
|
+
|
|
36
|
+
Find the real LLM call sites: `client.messages.create(...)` or
|
|
37
|
+
`client.messages.stream(...)` on a client constructed with `Anthropic()`
|
|
38
|
+
from the `anthropic` package.
|
|
39
|
+
|
|
40
|
+
- If the Anthropic calls go through a wrapper framework instead (LangChain,
|
|
41
|
+
CrewAI, the Claude Agent SDK, or similar), stop: instrument the wrapper
|
|
42
|
+
layer with its dedicated skill, never the underlying provider client.
|
|
43
|
+
- If the repo wraps its Anthropic client in a helper module (for example
|
|
44
|
+
`app/llm.py` exporting a shared client), plan to instrument at that one
|
|
45
|
+
place; do not edit every call site.
|
|
46
|
+
- If there are no Anthropic call sites at all, stop and report.
|
|
47
|
+
|
|
48
|
+
## Step 2: Install the SDK
|
|
49
|
+
|
|
50
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
51
|
+
(`uv.lock` means uv, `poetry.lock` means poetry, otherwise pip):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install moda-ai
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
With uv:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv add moda-ai
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
With poetry:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
poetry add moda-ai
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If `moda-ai` is already a dependency, skip the install. `moda-ai` includes
|
|
70
|
+
the OpenAI and Anthropic instrumentation packages; nothing else is needed
|
|
71
|
+
for this integration.
|
|
72
|
+
|
|
73
|
+
## Step 3: Initialize Moda once at startup, before the client is created
|
|
74
|
+
|
|
75
|
+
Instrumentation is automatic: once `moda.init` has run, every
|
|
76
|
+
`messages.create` / `messages.stream` call (including extended thinking and
|
|
77
|
+
tool use) is captured with no per-call changes.
|
|
78
|
+
|
|
79
|
+
`moda.init` is synchronous and must run before the Anthropic client is
|
|
80
|
+
constructed. If a `moda.init` call already exists, verify it runs at startup
|
|
81
|
+
before client creation and skip this step; never add a second call.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import os
|
|
85
|
+
|
|
86
|
+
import moda
|
|
87
|
+
from anthropic import Anthropic
|
|
88
|
+
|
|
89
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
90
|
+
|
|
91
|
+
client = Anthropic()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For web frameworks, initialize at import time of the application module (as
|
|
95
|
+
above) so the client-creation ordering holds; requirement: `moda.init` runs
|
|
96
|
+
before any `Anthropic()` construction.
|
|
97
|
+
|
|
98
|
+
## Step 4: Set conversation and user context
|
|
99
|
+
|
|
100
|
+
Set a stable conversation id before each LLM call so multi-turn
|
|
101
|
+
conversations group together, and a user id when one user can be identified.
|
|
102
|
+
|
|
103
|
+
For sequential code, use the module-level properties:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
moda.conversation_id = f"session_{session_id}"
|
|
107
|
+
moda.user_id = user_id
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For concurrent request handlers (any web server), use the scoped context
|
|
111
|
+
managers so overlapping requests do not leak context into each other:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from moda import set_conversation_id, set_user_id
|
|
115
|
+
|
|
116
|
+
with set_conversation_id(f"session_{session_id}"):
|
|
117
|
+
with set_user_id(user_id):
|
|
118
|
+
response = client.messages.create(
|
|
119
|
+
model="claude-sonnet-4-20250514",
|
|
120
|
+
max_tokens=1024,
|
|
121
|
+
messages=[{"role": "user", "content": user_message}],
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Reuse the session/thread identifier the app already has. If no session
|
|
126
|
+
identifier exists anywhere, the SDK falls back to hashing the first user
|
|
127
|
+
message plus the system prompt — acceptable only for simple chatbots that
|
|
128
|
+
pass full message history on every call. If this repo is an agent framework
|
|
129
|
+
or does not pass full history, an explicit conversation id is required; stop
|
|
130
|
+
and report if you cannot find a session identifier to use.
|
|
131
|
+
|
|
132
|
+
## Step 5: Flush before exit
|
|
133
|
+
|
|
134
|
+
Flush pending telemetry before the process exits (short-lived scripts) or on
|
|
135
|
+
shutdown (servers):
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
moda.flush()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Step 6: Validate
|
|
142
|
+
|
|
143
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
144
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
145
|
+
verify telemetry reaches Moda:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
moda doctor --online --json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The report should show recent traces. If it does not, confirm `MODA_API_KEY`
|
|
152
|
+
is available in the runtime environment and that `moda.init()` runs before
|
|
153
|
+
the Anthropic client is created.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Golden reference: Claude Agent SDK + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal Claude Agent SDK script.
|
|
4
|
+
Match this structure; adapt names to the target repository.
|
|
5
|
+
|
|
6
|
+
## agent.py
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
import moda
|
|
13
|
+
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
|
14
|
+
|
|
15
|
+
# Initialize Moda before creating the ClaudeSDKClient. When
|
|
16
|
+
# moda-claude-agent-sdk is installed, the instrumentor is enabled
|
|
17
|
+
# automatically.
|
|
18
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
19
|
+
|
|
20
|
+
# Explicit conversation and user context so all turns group together.
|
|
21
|
+
moda.conversation_id = "session_123"
|
|
22
|
+
moda.user_id = "user_456"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def main():
|
|
26
|
+
options = ClaudeAgentOptions(model="claude-sonnet-4-20250514")
|
|
27
|
+
|
|
28
|
+
async with ClaudeSDKClient(options=options) as client:
|
|
29
|
+
await client.query("What is the capital of France?")
|
|
30
|
+
|
|
31
|
+
# Consume the full stream: token usage arrives in the final
|
|
32
|
+
# ResultMessage.
|
|
33
|
+
async for msg in client.receive_response():
|
|
34
|
+
print(msg)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
asyncio.run(main())
|
|
38
|
+
|
|
39
|
+
# Flush pending telemetry before exit.
|
|
40
|
+
moda.flush()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Dependencies
|
|
44
|
+
|
|
45
|
+
Both packages are required; `moda-claude-agent-sdk` is not included with
|
|
46
|
+
`moda-ai`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install moda-ai moda-claude-agent-sdk claude-agent-sdk
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## What changed, and what did not
|
|
53
|
+
|
|
54
|
+
- `moda-ai` and `moda-claude-agent-sdk` added to the project dependencies.
|
|
55
|
+
- One `moda.init` call added at startup, before `ClaudeSDKClient` is
|
|
56
|
+
constructed.
|
|
57
|
+
- Conversation and user ids set before the agent runs.
|
|
58
|
+
- The agent code itself is unchanged; the instrumentor wraps
|
|
59
|
+
`ClaudeSDKClient` automatically.
|
|
60
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
61
|
+
runtime environment.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-python-claude-agent-sdk
|
|
3
|
+
name: "Integrate Moda with the Claude Agent SDK (Python)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: python
|
|
8
|
+
frameworks:
|
|
9
|
+
- claude-agent-sdk
|
|
10
|
+
providers: []
|
|
11
|
+
sdk_package: moda-claude-agent-sdk
|
|
12
|
+
summary: "Instrument Claude Agent SDK sessions (ClaudeSDKClient) with Moda via the dedicated moda-claude-agent-sdk instrumentor."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with the Claude Agent SDK (Python)
|
|
16
|
+
|
|
17
|
+
Follow the steps below in order. Each step has a stop condition; when you hit
|
|
18
|
+
one, stop and report instead of guessing. `EXAMPLE.md` in this directory shows
|
|
19
|
+
the complete finished integration; match its structure. For API details beyond
|
|
20
|
+
this skill, read https://docs.moda.dev/ingestion/providers/claude-agent-sdk.
|
|
21
|
+
|
|
22
|
+
## Safety rules (apply to every step)
|
|
23
|
+
|
|
24
|
+
- Only touch the repository you were launched in.
|
|
25
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
26
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
27
|
+
environment or a secret manager.
|
|
28
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
29
|
+
- Do not commit unless the user asks.
|
|
30
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
31
|
+
guessing.
|
|
32
|
+
|
|
33
|
+
## Why this integration needs a separate package
|
|
34
|
+
|
|
35
|
+
The Claude Agent SDK spawns Claude as a subprocess rather than making direct
|
|
36
|
+
Anthropic API calls, so standard Anthropic API instrumentation does not
|
|
37
|
+
fire. The `moda-claude-agent-sdk` package provides a dedicated instrumentor
|
|
38
|
+
that wraps `ClaudeSDKClient` to capture agent-level spans (token usage, tool
|
|
39
|
+
calls, turns, session metadata). The Claude Agent SDK is Python-only; if
|
|
40
|
+
this repository is Node.js, stop and report — there is no Node.js support.
|
|
41
|
+
|
|
42
|
+
## Step 1: Find the Claude Agent SDK call sites
|
|
43
|
+
|
|
44
|
+
Find where the application constructs `ClaudeSDKClient` (from
|
|
45
|
+
`claude_agent_sdk`). If there are none, stop and report: this skill only
|
|
46
|
+
applies to apps using the Claude Agent SDK.
|
|
47
|
+
|
|
48
|
+
## Step 2: Install the SDK packages
|
|
49
|
+
|
|
50
|
+
Install both `moda-ai` and `moda-claude-agent-sdk` with the package manager
|
|
51
|
+
this repository already uses (`uv.lock` means uv, `poetry.lock` means
|
|
52
|
+
poetry, otherwise pip). `moda-claude-agent-sdk` is NOT included with
|
|
53
|
+
`moda-ai`; it must be installed separately:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install moda-ai moda-claude-agent-sdk
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
With uv:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv add moda-ai moda-claude-agent-sdk
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
With poetry:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
poetry add moda-ai moda-claude-agent-sdk
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If both are already dependencies, skip the install.
|
|
72
|
+
|
|
73
|
+
## Step 3: Initialize Moda once at startup, before the client is created
|
|
74
|
+
|
|
75
|
+
`moda.init` is synchronous, automatically enables the Claude Agent SDK
|
|
76
|
+
instrumentor when `moda-claude-agent-sdk` is installed, and must run before
|
|
77
|
+
any `ClaudeSDKClient` is constructed. If a `moda.init` call already exists,
|
|
78
|
+
verify it runs at startup and skip this step; never add a second call.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import os
|
|
82
|
+
|
|
83
|
+
import moda
|
|
84
|
+
|
|
85
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Step 4: Set conversation and user context
|
|
89
|
+
|
|
90
|
+
Set a stable conversation id (and user id when one user can be identified)
|
|
91
|
+
before running the agent, so all turns of a session group together:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
moda.conversation_id = f"session_{session_id}"
|
|
95
|
+
moda.user_id = user_id
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Reuse the session identifier the app already has. If no session identifier
|
|
99
|
+
exists anywhere, stop and report; do not invent one.
|
|
100
|
+
|
|
101
|
+
## Step 5: Consume the full response stream
|
|
102
|
+
|
|
103
|
+
Token usage comes from the `ResultMessage` at the end of the stream. Verify
|
|
104
|
+
the code consumes the full async generator from `receive_response()`; if it
|
|
105
|
+
breaks out early, token counts will be missing. Do not restructure working
|
|
106
|
+
code — just confirm the stream is fully consumed, and report if it is not.
|
|
107
|
+
|
|
108
|
+
## Step 6: Flush before exit
|
|
109
|
+
|
|
110
|
+
Flush pending telemetry before the process exits:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
moda.flush()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Step 7: Validate
|
|
117
|
+
|
|
118
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
119
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
120
|
+
verify telemetry reaches Moda:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
moda doctor --online --json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The report should show recent traces. If it does not: confirm
|
|
127
|
+
`moda-claude-agent-sdk` is installed (it is separate from `moda-ai`), that
|
|
128
|
+
`moda.init()` runs before `ClaudeSDKClient` is created, and that
|
|
129
|
+
`MODA_API_KEY` is available in the runtime environment.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Golden reference: generic Python app + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a small Python chat script whose LLM
|
|
4
|
+
calls go through the `openai` client (automatically instrumented). Match
|
|
5
|
+
this structure; adapt names to the target repository.
|
|
6
|
+
|
|
7
|
+
## app/main.py
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
import moda
|
|
13
|
+
from moda import set_conversation_id, set_user_id
|
|
14
|
+
from openai import OpenAI
|
|
15
|
+
|
|
16
|
+
# Initialize Moda once, before any LLM client is created.
|
|
17
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
18
|
+
|
|
19
|
+
client = OpenAI()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def answer(session_id: str, user_id: str, message: str) -> str:
|
|
23
|
+
# Scoped context: safe under concurrent callers.
|
|
24
|
+
with set_conversation_id(f"session_{session_id}"):
|
|
25
|
+
with set_user_id(user_id):
|
|
26
|
+
response = client.chat.completions.create(
|
|
27
|
+
model="gpt-4o",
|
|
28
|
+
messages=[{"role": "user", "content": message}],
|
|
29
|
+
)
|
|
30
|
+
return response.choices[0].message.content
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
print(answer("demo-session", "user-123", "Hello!"))
|
|
34
|
+
|
|
35
|
+
# Flush pending telemetry before exit.
|
|
36
|
+
moda.flush()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## What changed, and what did not
|
|
40
|
+
|
|
41
|
+
- `moda-ai` added to the project dependencies.
|
|
42
|
+
- One `moda.init` call added at startup, before any LLM client is created.
|
|
43
|
+
- Call sites through instrumented clients are unchanged; only context
|
|
44
|
+
scoping was added around them.
|
|
45
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
46
|
+
runtime environment.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-python-generic
|
|
3
|
+
name: "Integrate the Moda SDK (Python, any provider)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: python
|
|
8
|
+
frameworks: []
|
|
9
|
+
providers: []
|
|
10
|
+
sdk_package: moda-ai
|
|
11
|
+
summary: "General Moda SDK integration for a Python app when no specific framework or provider skill matches."
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Integrate the Moda SDK (Python, any provider)
|
|
15
|
+
|
|
16
|
+
This is the general Python integration path, used when no framework- or
|
|
17
|
+
provider-specific skill matches. Follow the steps below in order. Each step
|
|
18
|
+
has a stop condition; when you hit one, stop and report instead of guessing.
|
|
19
|
+
`EXAMPLE.md` in this directory shows a complete finished integration; match
|
|
20
|
+
its structure. For API details beyond this skill, read
|
|
21
|
+
https://docs.moda.dev/ingestion/moda-sdk.
|
|
22
|
+
|
|
23
|
+
## Safety rules (apply to every step)
|
|
24
|
+
|
|
25
|
+
- Only touch the repository you were launched in.
|
|
26
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
27
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
28
|
+
environment or a secret manager.
|
|
29
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
30
|
+
- Do not commit unless the user asks.
|
|
31
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
32
|
+
guessing.
|
|
33
|
+
|
|
34
|
+
## Step 1: Find the real LLM call sites and pick the right surface
|
|
35
|
+
|
|
36
|
+
Search the repository for LLM calls and classify them:
|
|
37
|
+
|
|
38
|
+
- Calls through an instrumented provider or framework: automatically
|
|
39
|
+
captured once Moda is initialized (Step 3); no per-call changes. The
|
|
40
|
+
Python SDK's `Instruments` set covers providers including OpenAI,
|
|
41
|
+
Anthropic, Cohere, Mistral, Groq, Ollama, Bedrock, VertexAI, and
|
|
42
|
+
frameworks including LangChain, LlamaIndex, Haystack, and CrewAI (full
|
|
43
|
+
table: https://docs.moda.dev/ingestion/moda-sdk). OpenAI-compatible
|
|
44
|
+
gateways (for example OpenRouter used through the `openai` client with a
|
|
45
|
+
custom `base_url`) count as OpenAI here.
|
|
46
|
+
- Calls through LangChain or the Claude Agent SDK: stop — use the dedicated
|
|
47
|
+
skill instead; never instrument the underlying provider client when a
|
|
48
|
+
wrapper framework is in use.
|
|
49
|
+
- Direct HTTP calls to a provider outside the instruments table: the Python
|
|
50
|
+
SDK does not capture these automatically. Note them in your report and
|
|
51
|
+
point the user at https://docs.moda.dev/ingestion/direct-api; do not
|
|
52
|
+
build a custom ingestion path yourself.
|
|
53
|
+
|
|
54
|
+
If the repository has no LLM call sites at all, stop and report.
|
|
55
|
+
|
|
56
|
+
## Step 2: Install the SDK
|
|
57
|
+
|
|
58
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
59
|
+
(`uv.lock` means uv, `poetry.lock` means poetry, otherwise pip):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install moda-ai
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
With uv:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv add moda-ai
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
With poetry:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
poetry add moda-ai
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If `moda-ai` is already a dependency, skip the install.
|
|
78
|
+
|
|
79
|
+
## Step 3: Initialize Moda once at startup
|
|
80
|
+
|
|
81
|
+
`moda.init` is synchronous and must run once, at startup, before any LLM
|
|
82
|
+
client is created. If a `moda.init` call already exists, verify it runs at
|
|
83
|
+
startup and skip this step; never add a second call.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import os
|
|
87
|
+
|
|
88
|
+
import moda
|
|
89
|
+
|
|
90
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Step 4: Set conversation and user context
|
|
94
|
+
|
|
95
|
+
Set a stable conversation id before each LLM call so multi-turn
|
|
96
|
+
conversations group together, and a user id when one user can be identified.
|
|
97
|
+
If one user reaches the same agent through several channels, use the same
|
|
98
|
+
user id across channels.
|
|
99
|
+
|
|
100
|
+
For sequential code, use the module-level properties:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
moda.conversation_id = f"session_{session_id}"
|
|
104
|
+
moda.user_id = user_id
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
For concurrent request handlers (any web server), use the scoped context
|
|
108
|
+
managers so overlapping requests do not leak context into each other:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from moda import set_conversation_id, set_user_id
|
|
112
|
+
|
|
113
|
+
with set_conversation_id(f"session_{session_id}"):
|
|
114
|
+
with set_user_id(user_id):
|
|
115
|
+
response = make_llm_call(user_message)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Reuse the session/thread identifier the app already has. If no session
|
|
119
|
+
identifier exists anywhere, the SDK falls back to hashing the first user
|
|
120
|
+
message plus the system prompt — acceptable only for simple chatbots that
|
|
121
|
+
pass full message history on every call. If this repo is an agent framework
|
|
122
|
+
or does not pass full history, an explicit conversation id is required; stop
|
|
123
|
+
and report if you cannot find a session identifier to use.
|
|
124
|
+
|
|
125
|
+
## Step 5: Flush before exit
|
|
126
|
+
|
|
127
|
+
Flush pending telemetry before the process exits (short-lived scripts) or on
|
|
128
|
+
shutdown (servers):
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
moda.flush()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Step 6: Validate
|
|
135
|
+
|
|
136
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
137
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
138
|
+
verify telemetry reaches Moda:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
moda doctor --online --json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The report should show recent traces. If it does not, confirm `MODA_API_KEY`
|
|
145
|
+
is available in the runtime environment and that `moda.init()` runs before
|
|
146
|
+
the first LLM client is created.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Golden reference: Python LangChain agent + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal LangChain app. Match this
|
|
4
|
+
structure; adapt names to the target repository. Note what is absent: no
|
|
5
|
+
LangChain callbacks or handler classes for Moda — `moda.init()` enables all
|
|
6
|
+
instruments (including LangChain) by default, and the only per-run change is
|
|
7
|
+
the explicit conversation id wrapper.
|
|
8
|
+
|
|
9
|
+
## app/agent.py
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
import moda
|
|
15
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
16
|
+
from langchain_openai import ChatOpenAI
|
|
17
|
+
from moda import set_conversation_id, set_user_id
|
|
18
|
+
|
|
19
|
+
# Initialize Moda once, before LangChain models are constructed. moda.init
|
|
20
|
+
# enables all instruments (including LangChain) by default.
|
|
21
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
22
|
+
|
|
23
|
+
model = ChatOpenAI(model="gpt-4o")
|
|
24
|
+
prompt = ChatPromptTemplate.from_messages([
|
|
25
|
+
("system", "You are a helpful assistant."),
|
|
26
|
+
("human", "{input}"),
|
|
27
|
+
])
|
|
28
|
+
chain = prompt | model
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_agent(session_id: str, user_id: str, user_query: str):
|
|
32
|
+
# Explicit conversation id around the whole run: every internal LLM call
|
|
33
|
+
# made during this invocation shares it. Automatic detection does NOT
|
|
34
|
+
# work for agent frameworks.
|
|
35
|
+
with set_conversation_id(f"agent_session_{session_id}"):
|
|
36
|
+
with set_user_id(user_id):
|
|
37
|
+
return chain.invoke({"input": user_query})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## app/main.py
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import moda
|
|
44
|
+
|
|
45
|
+
from app.agent import run_agent
|
|
46
|
+
|
|
47
|
+
result = run_agent("demo-session", "user-123", "What is Python?")
|
|
48
|
+
print(result.content)
|
|
49
|
+
|
|
50
|
+
# Flush pending telemetry before exit.
|
|
51
|
+
moda.flush()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## What changed, and what did not
|
|
55
|
+
|
|
56
|
+
- `moda-ai` added to the project dependencies.
|
|
57
|
+
- One `moda.init` call added at startup, before any LangChain model is
|
|
58
|
+
constructed.
|
|
59
|
+
- Each agent/chain entry point is wrapped in the scoped context managers
|
|
60
|
+
with an explicit conversation id; the LangChain code itself is unchanged.
|
|
61
|
+
- No LangChain callbacks or handler classes were added for Moda.
|
|
62
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
63
|
+
runtime environment.
|