Skip to content

Commit feba20b

Browse files
authored
Merge branch 'main' into voyageai_context_and_multimodal
2 parents 1ea4923 + 693a352 commit feba20b

File tree

212 files changed

+25875
-4245
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

212 files changed

+25875
-4245
lines changed

.github/workflows/core-unit-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ jobs:
4747
{"test_suite": "test_llm_clients.py"},
4848
{"test_suite": "test_letta_agent_batch.py"},
4949
{"test_suite": "test_providers.py"},
50+
{"test_suite": "test_server_providers.py"},
5051
{"test_suite": "test_sources.py"},
5152
{"test_suite": "sdk/"},
5253
{"test_suite": "mcp_tests/"},

.github/workflows/send-message-integration-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ jobs:
3737
"matrix": {
3838
"config_file": [
3939
"openai-gpt-4o-mini.json",
40+
"claude-4-5-sonnet.json",
4041
"claude-4-sonnet-extended.json",
41-
"claude-3-5-sonnet.json",
4242
"claude-3-7-sonnet-extended.json",
4343
"gemini-1.5-pro.json",
4444
"gemini-2.5-pro.json",

README.md

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ In the example below, we'll create a stateful agent with two memory blocks, one
4343
### Python
4444
```python
4545
from letta_client import Letta
46+
import os
4647

47-
client = Letta(token="LETTA_API_KEY")
48-
# client = Letta(base_url="http://localhost:8283") # if self-hosting, set your base_url
48+
# Connect to Letta Cloud (get your API key at https://app.letta.com/api-keys)
49+
client = Letta(token=os.getenv("LETTA_API_KEY"))
50+
# client = Letta(base_url="http://localhost:8283", embedding="openai/text-embedding-3-small") # if self-hosting, set base_url and embedding
4951

5052
agent_state = client.agents.create(
5153
model="openai/gpt-4.1",
52-
embedding="openai/text-embedding-3-small",
5354
memory_blocks=[
5455
{
5556
"label": "human",
@@ -85,12 +86,12 @@ for message in response.messages:
8586
```typescript
8687
import { LettaClient } from '@letta-ai/letta-client'
8788

88-
const client = new LettaClient({ token: "LETTA_API_KEY" });
89-
// const client = new LettaClient({ baseUrl: "http://localhost:8283" }); // if self-hosting, set your baseUrl
89+
// Connect to Letta Cloud (get your API key at https://app.letta.com/api-keys)
90+
const client = new LettaClient({ token: process.env.LETTA_API_KEY });
91+
// const client = new LettaClient({ baseUrl: "http://localhost:8283", embedding: "openai/text-embedding-3-small" }); // if self-hosting
9092

9193
const agentState = await client.agents.create({
9294
model: "openai/gpt-4.1",
93-
embedding: "openai/text-embedding-3-small",
9495
memoryBlocks: [
9596
{
9697
label: "human",
@@ -150,7 +151,6 @@ shared_block = client.blocks.create(
150151
# create a supervisor agent
151152
supervisor_agent = client.agents.create(
152153
model="anthropic/claude-3-5-sonnet-20241022",
153-
embedding="openai/text-embedding-3-small",
154154
# blocks created for this agent
155155
memory_blocks=[{"label": "persona", "value": "I am a supervisor"}],
156156
# pre-existing shared block that is "attached" to this agent
@@ -160,7 +160,6 @@ supervisor_agent = client.agents.create(
160160
# create a worker agent
161161
worker_agent = client.agents.create(
162162
model="openai/gpt-4.1-mini",
163-
embedding="openai/text-embedding-3-small",
164163
# blocks created for this agent
165164
memory_blocks=[{"label": "persona", "value": "I am a worker"}],
166165
# pre-existing shared block that is "attached" to this agent
@@ -180,7 +179,6 @@ const sharedBlock = await client.blocks.create({
180179
// create a supervisor agent
181180
const supervisorAgent = await client.agents.create({
182181
model: "anthropic/claude-3-5-sonnet-20241022",
183-
embedding: "openai/text-embedding-3-small",
184182
// blocks created for this agent
185183
memoryBlocks: [{ label: "persona", value: "I am a supervisor" }],
186184
// pre-existing shared block that is "attached" to this agent
@@ -190,7 +188,6 @@ const supervisorAgent = await client.agents.create({
190188
// create a worker agent
191189
const workerAgent = await client.agents.create({
192190
model: "openai/gpt-4.1-mini",
193-
embedding: "openai/text-embedding-3-small",
194191
// blocks created for this agent
195192
memoryBlocks: [{ label: "persona", value: "I am a worker" }],
196193
// pre-existing shared block that is "attached" to this agent
@@ -277,7 +274,6 @@ tool = client.tools.add_mcp_tool(
277274
# Create agent with MCP tool attached
278275
agent_state = client.agents.create(
279276
model="openai/gpt-4o-mini",
280-
embedding="openai/text-embedding-3-small",
281277
tool_ids=[tool.id]
282278
)
283279

@@ -310,7 +306,6 @@ const tool = await client.tools.addMcpTool("weather-server", "get_weather");
310306
// Create agent with MCP tool
311307
const agentState = await client.agents.create({
312308
model: "openai/gpt-4o-mini",
313-
embedding: "openai/text-embedding-3-small",
314309
toolIds: [tool.id]
315310
});
316311

@@ -333,17 +328,12 @@ Once you attach a folder to an agent, the agent will be able to use filesystem t
333328

334329
<details>
335330
<summary>View code snippets</summary>
336-
331+
337332
### Python
338333
```python
339-
# get an available embedding_config
340-
embedding_configs = client.embedding_models.list()
341-
embedding_config = embedding_configs[0]
342-
343-
# create the folder
334+
# create the folder (embeddings managed automatically by Letta Cloud)
344335
folder = client.folders.create(
345-
name="my_folder",
346-
embedding_config=embedding_config
336+
name="my_folder"
347337
)
348338

349339
# upload a file into the folder
@@ -381,14 +371,9 @@ for message in response.messages:
381371

382372
### TypeScript / Node.js
383373
```typescript
384-
// get an available embedding_config
385-
const embeddingConfigs = await client.embeddingModels.list()
386-
const embeddingConfig = embeddingConfigs[0];
387-
388-
// create the folder
374+
// create the folder (embeddings managed automatically by Letta Cloud)
389375
const folder = await client.folders.create({
390-
name: "my_folder",
391-
embeddingConfig: embeddingConfig
376+
name: "my_folder"
392377
});
393378

394379
// upload a file into the folder
@@ -445,7 +430,7 @@ When agents need to execute multiple tool calls or perform complex operations (l
445430

446431
<details>
447432
<summary>View code snippets</summary>
448-
433+
449434
### Python
450435
```python
451436
stream = client.agents.messages.create_stream(
@@ -530,7 +515,7 @@ Letta is an open source project built by over a hundred contributors. There are
530515

531516
* [**Join the Discord**](https://discord.gg/letta): Chat with the Letta devs and other AI developers.
532517
* [**Chat on our forum**](https://forum.letta.com/): If you're not into Discord, check out our developer forum.
533-
* **Follow our socials**: [Twitter/X](https://twitter.com/Letta_AI), [LinkedIn](https://www.linkedin.com/in/letta), [YouTube](https://www.youtube.com/@letta-ai)
518+
* **Follow our socials**: [Twitter/X](https://twitter.com/Letta_AI), [LinkedIn](https://www.linkedin.com/in/letta), [YouTube](https://www.youtube.com/@letta-ai)
534519

535520
---
536521

WEBHOOK_SETUP.md

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# Step Completion Webhook
2+
3+
This feature allows you to receive webhook notifications whenever an agent step completes in the Letta agent loop.
4+
5+
## Architecture
6+
7+
The webhook service integrates with Letta's execution architecture in two ways:
8+
9+
### 1. With Temporal (Recommended)
10+
11+
When using Temporal for agent workflows, webhook calls are wrapped as Temporal activities, providing:
12+
- Built-in retry logic with configurable timeouts
13+
- Full observability in Temporal UI
14+
- Durability guarantees
15+
- Consistent error handling
16+
- Activity history and replay capability
17+
18+
Webhooks are triggered after the `create_step` activity completes in the Temporal workflow.
19+
20+
### 2. Without Temporal (Direct Execution)
21+
22+
For direct agent execution (non-Temporal), webhooks are called directly from the `StepManager` service methods:
23+
- `update_step_success_async()` - When step completes successfully
24+
- `update_step_error_async()` - When step fails with an error
25+
- `update_step_cancelled_async()` - When step is cancelled
26+
27+
Webhooks are sent after the step status is committed to the database.
28+
29+
### Common Behavior
30+
31+
In **both** cases:
32+
- ✅ Webhook failures do not prevent step completion
33+
- ✅ Step is always marked as complete in the database first
34+
- ✅ Webhook delivery is logged for debugging
35+
- ✅ Same authentication and payload format
36+
37+
## Configuration
38+
39+
Set the following environment variables to enable webhook notifications:
40+
41+
### Required
42+
43+
- **`STEP_COMPLETE_WEBHOOK`**: The URL endpoint that will receive POST requests when steps complete.
44+
- Example: `https://your-app.com/api/webhooks/step-complete`
45+
46+
### Optional
47+
48+
- **`STEP_COMPLETE_KEY`**: A secret key used for authentication.
49+
- When set, the webhook service will include this in an `Authorization` header as `Bearer {key}`
50+
- Example: `your-secret-webhook-key-12345`
51+
52+
## Webhook Payload
53+
54+
When a step completes, the webhook service will send a POST request with the following JSON payload:
55+
56+
```json
57+
{
58+
"step_id": "step-01234567-89ab-cdef-0123-456789abcdef"
59+
}
60+
```
61+
62+
## Authentication
63+
64+
If `STEP_COMPLETE_KEY` is configured, requests will include an Authorization header:
65+
66+
```
67+
Authorization: Bearer your-secret-webhook-key-12345
68+
```
69+
70+
Your webhook endpoint should validate this key to ensure requests are coming from your Letta instance.
71+
72+
## Example Webhook Endpoint
73+
74+
Here's a simple example of a webhook endpoint (using FastAPI):
75+
76+
```python
77+
from fastapi import FastAPI, Header, HTTPException
78+
from pydantic import BaseModel
79+
import os
80+
81+
app = FastAPI()
82+
83+
class StepCompletePayload(BaseModel):
84+
step_id: str
85+
86+
WEBHOOK_SECRET = os.getenv("STEP_COMPLETE_KEY")
87+
88+
@app.post("/api/webhooks/step-complete")
89+
async def handle_step_complete(
90+
payload: StepCompletePayload,
91+
authorization: str = Header(None)
92+
):
93+
# Validate the webhook key
94+
if WEBHOOK_SECRET:
95+
if not authorization or not authorization.startswith("Bearer "):
96+
raise HTTPException(status_code=401, detail="Missing authorization")
97+
98+
token = authorization.replace("Bearer ", "")
99+
if token != WEBHOOK_SECRET:
100+
raise HTTPException(status_code=401, detail="Invalid authorization")
101+
102+
# Process the step completion
103+
print(f"Step completed: {payload.step_id}")
104+
105+
# You can now:
106+
# - Log the step completion
107+
# - Trigger downstream processes
108+
# - Update your application state
109+
# - Send notifications
110+
111+
return {"status": "success"}
112+
```
113+
114+
## Usage Example
115+
116+
```bash
117+
# Set environment variables
118+
export STEP_COMPLETE_WEBHOOK="https://your-app.com/api/webhooks/step-complete"
119+
export STEP_COMPLETE_KEY="your-secret-webhook-key-12345"
120+
121+
# Start your Letta server
122+
python -m letta.server
123+
```
124+
125+
## When Webhooks Are Sent
126+
127+
Webhooks are triggered when a step reaches a terminal state:
128+
129+
1. **Success** - Step completed successfully (`StepStatus.SUCCESS`)
130+
2. **Error** - Step failed with an error (`StepStatus.FAILED`)
131+
3. **Cancelled** - Step was cancelled (`StepStatus.CANCELLED`)
132+
133+
All three states trigger the webhook with the same payload containing just the `step_id`.
134+
135+
## Behavior
136+
137+
- **No webhook URL configured**: The service will skip sending notifications (logged at debug level)
138+
- **Webhook call succeeds**: Returns status 200-299, logged at info level
139+
- **Webhook timeout**: Returns error, logged at warning level (does not fail the step)
140+
- **HTTP error**: Returns non-2xx status, logged at warning level (does not fail the step)
141+
- **Other errors**: Logged at error level (does not fail the step)
142+
143+
**Important**: Webhook failures do not prevent step completion. The step will be marked as complete in the database regardless of webhook delivery status. This ensures system reliability - your webhook endpoint being down will not block agent execution.
144+
145+
## Testing
146+
147+
To test the webhook functionality:
148+
149+
1. Set up a webhook endpoint (you can use [webhook.site](https://webhook.site) for testing)
150+
2. Configure the environment variables
151+
3. Run an agent and observe webhook calls when steps complete
152+
153+
```bash
154+
# Example using webhook.site
155+
export STEP_COMPLETE_WEBHOOK="https://webhook.site/your-unique-url"
156+
export STEP_COMPLETE_KEY="test-key-123"
157+
158+
# Run tests
159+
python -m pytest apps/core/letta/services/webhook_service_test.py -v
160+
```
161+
162+
## Implementation Details
163+
164+
The webhook notification is sent after:
165+
1. The step is persisted to the database
166+
2. Step metrics are recorded
167+
168+
This ensures that the step data is fully committed before external systems are notified.
169+
170+
### Temporal Integration
171+
172+
When using Temporal, the webhook call is executed as a separate activity (`send_step_complete_webhook`) with the following configuration:
173+
174+
- **Start-to-close timeout**: 15 seconds
175+
- **Schedule-to-close timeout**: 30 seconds
176+
- **Retry behavior**: Wrapped in try-catch to prevent workflow failure on webhook errors
177+
178+
This allows you to monitor webhook delivery in the Temporal UI and get detailed visibility into any failures.
179+
180+
### File Locations
181+
182+
**Core Service:**
183+
- `apps/core/letta/services/webhook_service.py` - HTTP client for webhook delivery
184+
185+
**Temporal Integration:**
186+
- `apps/core/letta/agents/temporal/activities/send_webhook.py` - Temporal activity wrapper
187+
- `apps/core/letta/agents/temporal/temporal_agent_workflow.py` - Workflow integration
188+
- `apps/core/letta/agents/temporal/constants.py` - Timeout constants
189+
190+
**Non-Temporal Integration:**
191+
- `apps/core/letta/services/step_manager.py` - Direct calls in update_step_* methods
192+
193+
**Tests:**
194+
- `apps/core/letta/services/webhook_service_test.py` - Unit tests

0 commit comments

Comments
 (0)