langgraph-api 0.2.134__tar.gz → 0.2.137__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.
Potentially problematic release.
This version of langgraph-api might be problematic. Click here for more details.
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/PKG-INFO +1 -1
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/benchmark/Makefile +8 -0
- langgraph_api-0.2.137/benchmark/clean.js +87 -0
- langgraph_api-0.2.137/benchmark/update-revision.js +148 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/healthcheck.py +1 -0
- langgraph_api-0.2.137/langgraph_api/__init__.py +1 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/assistants.py +10 -9
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/runs.py +10 -6
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/store.py +1 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/asyncio.py +10 -21
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/auth/custom.py +8 -5
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/auth/langsmith/backend.py +4 -3
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/auth/langsmith/client.py +7 -13
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/config.py +7 -4
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/cron_scheduler.py +5 -6
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/http.py +3 -7
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/remote.py +28 -27
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/sse.py +2 -3
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/ui.py +6 -5
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/logging.py +9 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/models/run.py +4 -4
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/route.py +2 -1
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/serde.py +1 -2
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/store.py +1 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/stream.py +20 -11
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/worker.py +4 -3
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/pyproject.toml +9 -1
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/uv.lock +6 -6
- langgraph_api-0.2.134/langgraph_api/__init__.py +0 -1
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/.gitignore +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/LICENSE +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/Makefile +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/README.md +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/benchmark/.gitignore +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/benchmark/README.md +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/benchmark/burst.js +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/benchmark/graphs.js +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/benchmark/package.json +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/benchmark/ramp.js +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/benchmark/weather.js +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/constraints.txt +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/forbidden.txt +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/mcp.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/meta.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/threads.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/api/ui.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/asgi_transport.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/auth/studio_user.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/cli.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/command.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/executor_entrypoint.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/feature_flags.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/graph.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/http_metrics.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/.prettierrc +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/base.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/build.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/client.http.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/client.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/errors.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/global.d.ts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/package.json +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/schema.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/src/graph.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/src/load.hooks.mjs +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/src/preload.mjs +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/src/utils/files.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/traceblock.mts +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/tsconfig.json +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/js/yarn.lock +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/metadata.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/middleware/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/middleware/http_logger.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/middleware/private_network.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/middleware/request_id.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/models/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/queue_entrypoint.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/schema.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/server.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/state.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/thread_ttl.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/traceblock.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/tunneling/cloudflare.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/utils/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/utils/cache.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/utils/config.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/utils/future.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/utils/headers.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/utils/uuids.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/utils.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/validation.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_api/webhook.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/__init__.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/checkpoint.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/database.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/lifespan.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/metrics.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/ops.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/queue.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/retry.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/langgraph_runtime/store.py +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/logging.json +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/openapi.json +0 -0
- {langgraph_api-0.2.134 → langgraph_api-0.2.137}/scripts/create_license.py +0 -0
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
# Benchmark commands
|
|
2
2
|
benchmark-burst:
|
|
3
|
+
make benchmark-reset
|
|
3
4
|
k6 run burst.js
|
|
4
5
|
|
|
5
6
|
benchmark-ramp:
|
|
7
|
+
make benchmark-reset
|
|
6
8
|
k6 run --out json=raw_data_$(shell date +%Y-%m-%dT%H-%M-%S).json ramp.js
|
|
7
9
|
|
|
8
10
|
benchmark-charts:
|
|
9
11
|
npm install
|
|
10
12
|
node graphs.js $(shell ls -t raw_data_*.json | head -1) true
|
|
11
13
|
|
|
14
|
+
benchmark-reset:
|
|
15
|
+
node clean.js
|
|
16
|
+
|
|
17
|
+
benchmark-new-revision:
|
|
18
|
+
node update-revision.js
|
|
19
|
+
|
|
12
20
|
benchmark-clean:
|
|
13
21
|
rm -f results_*.json summary_*.json raw_data_*.json *_chart_*.png
|
|
14
22
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Delete all threads and runs from the last benchmark run for consistent tests
|
|
3
|
+
* The default benchmark server has a thread TTL of one hour that should clean things up too so this doesn't run too long.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// URL of your LangGraph server
|
|
7
|
+
const BASE_URL = process.env.BASE_URL || 'http://localhost:9123';
|
|
8
|
+
// LangSmith API key only needed with a custom server endpoint
|
|
9
|
+
const LANGSMITH_API_KEY = process.env.LANGSMITH_API_KEY;
|
|
10
|
+
|
|
11
|
+
async function clean() {
|
|
12
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
13
|
+
if (LANGSMITH_API_KEY) {
|
|
14
|
+
headers['x-api-key'] = LANGSMITH_API_KEY;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const searchUrl = `${BASE_URL}/threads/search`;
|
|
18
|
+
let totalDeleted = 0;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
console.log('Starting thread cleanup...');
|
|
22
|
+
|
|
23
|
+
while (true) {
|
|
24
|
+
try {
|
|
25
|
+
// Get the next page of threads
|
|
26
|
+
console.log('Searching for threads...');
|
|
27
|
+
const searchResponse = await fetch(searchUrl, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers,
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
limit: 1000
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!searchResponse.ok) {
|
|
36
|
+
throw new Error(`Search request failed: ${searchResponse.status} ${searchResponse.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const threads = await searchResponse.json();
|
|
40
|
+
|
|
41
|
+
// If no threads found, we're done
|
|
42
|
+
if (!threads || threads.length === 0) {
|
|
43
|
+
console.log('No more threads found.');
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`Found ${threads.length} threads to delete`);
|
|
48
|
+
|
|
49
|
+
// Delete each thread
|
|
50
|
+
for (const thread of threads) {
|
|
51
|
+
try {
|
|
52
|
+
const deleteUrl = `${BASE_URL}/threads/${thread.thread_id}`;
|
|
53
|
+
const deleteResponse = await fetch(deleteUrl, {
|
|
54
|
+
method: 'DELETE',
|
|
55
|
+
headers
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!deleteResponse.ok) {
|
|
59
|
+
console.error(`Failed to delete thread ${thread.thread_id}: ${deleteResponse.status} ${deleteResponse.statusText}`);
|
|
60
|
+
} else {
|
|
61
|
+
totalDeleted++;
|
|
62
|
+
}
|
|
63
|
+
} catch (deleteError) {
|
|
64
|
+
console.error(`Error deleting thread ${thread.thread_id}:`, deleteError.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(`Deleted ${threads.length} threads in this batch`);
|
|
69
|
+
|
|
70
|
+
} catch (batchError) {
|
|
71
|
+
console.error('Error in batch processing:', batchError.message);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(`Cleanup completed. Total threads deleted: ${totalDeleted}`);
|
|
77
|
+
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Fatal error during cleanup:', error.message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
clean().catch(error => {
|
|
85
|
+
console.error('Unhandled error:', error.message);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Trigger a new revision deployment and wait for it to complete
|
|
3
|
+
* This ensures the benchmark instance is running the latest code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// LangSmith API endpoints and credentials
|
|
7
|
+
const DEPLOYMENT_ID = process.env.DEPLOYMENT_ID || 'a23f03ff-6d4d-4efd-8149-bb5a7f3b95cf'; // jdr-benchmark deployment id
|
|
8
|
+
const LANGSMITH_API_KEY = process.env.LANGSMITH_API_KEY;
|
|
9
|
+
const API_BASE = 'https://api.host.langchain.com/v1';
|
|
10
|
+
|
|
11
|
+
// Deployment configuration
|
|
12
|
+
const REVISION_CONFIG = {
|
|
13
|
+
repo_path: "langgraph.json",
|
|
14
|
+
env_vars: [
|
|
15
|
+
{
|
|
16
|
+
name: "N_JOBS_PER_WORKER",
|
|
17
|
+
value: "100",
|
|
18
|
+
value_from: "secret",
|
|
19
|
+
type: "secret"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
shareable: false,
|
|
23
|
+
container_spec: {
|
|
24
|
+
cpu: null,
|
|
25
|
+
memory_mb: null,
|
|
26
|
+
min_scale: 10,
|
|
27
|
+
max_scale: null
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Expected deployment statuses in order
|
|
32
|
+
const EXPECTED_STATUSES = ['CREATED', 'AWAITING_BUILD', 'BUILDING', 'AWAITING_DEPLOY', 'DEPLOYING', 'DEPLOYED', 'QUEUED'];
|
|
33
|
+
const FINAL_STATUS = 'DEPLOYED';
|
|
34
|
+
const POLL_INTERVAL = 10000; // 10 seconds
|
|
35
|
+
const MAX_WAIT_TIME = 30 * 60 * 1000; // 30 minutes
|
|
36
|
+
|
|
37
|
+
async function updateRevision() {
|
|
38
|
+
if (!LANGSMITH_API_KEY) {
|
|
39
|
+
console.error('LANGSMITH_API_KEY environment variable is required');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const headers = {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
'x-api-key': LANGSMITH_API_KEY
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const createUrl = `${API_BASE}/projects/${DEPLOYMENT_ID}/revisions`;
|
|
49
|
+
const pollUrl = `${API_BASE}/projects/${DEPLOYMENT_ID}/revisions?limit=1&offset=0`;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
console.log('Triggering new revision deployment...');
|
|
53
|
+
|
|
54
|
+
// Step 1: Create new revision
|
|
55
|
+
const createResponse = await fetch(createUrl, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers,
|
|
58
|
+
body: JSON.stringify(REVISION_CONFIG)
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (createResponse.status === 409) {
|
|
62
|
+
console.log('⚠️ A new revision is already in progress. Continuing to poll existing deployment...');
|
|
63
|
+
} else if (!createResponse.ok) {
|
|
64
|
+
throw new Error(`Failed to create revision: ${createResponse.status} ${createResponse.statusText}`);
|
|
65
|
+
} else {
|
|
66
|
+
const newRevision = await createResponse.json();
|
|
67
|
+
const revisionId = newRevision.resource.latest_revision.hosted_langserve_revision_id;
|
|
68
|
+
console.log(`✓ New revision created: ${revisionId}`);
|
|
69
|
+
console.log(`Initial status: ${newRevision.status}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Step 2: Poll until deployment is complete
|
|
73
|
+
console.log('\nPolling deployment status...');
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
let lastStatus = null;
|
|
76
|
+
|
|
77
|
+
while (true) {
|
|
78
|
+
let pollResponse;
|
|
79
|
+
try {
|
|
80
|
+
pollResponse = await fetch(pollUrl, {
|
|
81
|
+
method: 'GET',
|
|
82
|
+
headers
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (!pollResponse.ok) {
|
|
86
|
+
throw new Error(`Failed to poll revisions: ${pollResponse.status} ${pollResponse.statusText}`);
|
|
87
|
+
}
|
|
88
|
+
} catch (pollError) {
|
|
89
|
+
console.error('Error calling poll endpoint (will retry):', pollError.message);
|
|
90
|
+
// Wait before next poll and continue
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Parse response and handle deployment logic (these errors should still be thrown)
|
|
96
|
+
const revisions = await pollResponse.json();
|
|
97
|
+
|
|
98
|
+
if (!revisions || revisions.length === 0) {
|
|
99
|
+
throw new Error('No revisions found');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const latestRevision = revisions[0];
|
|
103
|
+
const currentStatus = latestRevision.status;
|
|
104
|
+
|
|
105
|
+
// Log status changes
|
|
106
|
+
if (currentStatus !== lastStatus) {
|
|
107
|
+
const timestamp = new Date().toISOString();
|
|
108
|
+
console.log(`[${timestamp}] Status: ${currentStatus}`);
|
|
109
|
+
|
|
110
|
+
if (latestRevision.status_message) {
|
|
111
|
+
console.log(` Message: ${latestRevision.status_message}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lastStatus = currentStatus;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check if deployment is complete
|
|
118
|
+
if (currentStatus === FINAL_STATUS) {
|
|
119
|
+
console.log(`\n✓ Deployment completed successfully!`);
|
|
120
|
+
console.log(`Revision ID: ${latestRevision.id}`);
|
|
121
|
+
console.log(`Total time: ${Math.round((Date.now() - startTime) / 1000)}s`);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check for failure statuses
|
|
126
|
+
if (!EXPECTED_STATUSES.includes(currentStatus)) {
|
|
127
|
+
throw new Error(`Deployment failed with status: ${currentStatus}${latestRevision.status_message ? ` - ${latestRevision.status_message}` : ''}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check timeout
|
|
131
|
+
if (Date.now() - startTime > MAX_WAIT_TIME) {
|
|
132
|
+
throw new Error(`Deployment timeout after ${MAX_WAIT_TIME / 60000} minutes. Last status: ${currentStatus}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Wait before next poll
|
|
136
|
+
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Fatal error during revision update:', error.message);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
updateRevision().catch(error => {
|
|
146
|
+
console.error('Unhandled error:', error.message);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.137"
|
|
@@ -82,9 +82,12 @@ def _get_configurable_jsonschema(graph: Pregel) -> dict:
|
|
|
82
82
|
json_schema["properties"].pop(key, None)
|
|
83
83
|
# The type name of the configurable type is not preserved.
|
|
84
84
|
# We'll add it back to the schema if we can.
|
|
85
|
-
if
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
if (
|
|
86
|
+
hasattr(graph, "config_type")
|
|
87
|
+
and graph.config_type is not None
|
|
88
|
+
and hasattr(graph.config_type, "__name__")
|
|
89
|
+
):
|
|
90
|
+
json_schema["title"] = graph.config_type.__name__
|
|
88
91
|
return json_schema
|
|
89
92
|
# If the schema does not have a configurable field, return an empty schema.
|
|
90
93
|
return {}
|
|
@@ -263,18 +266,16 @@ async def get_assistant_graph(
|
|
|
263
266
|
drawable_graph = await graph.fetch_graph(xray=xray)
|
|
264
267
|
json_graph = drawable_graph.to_json()
|
|
265
268
|
for node in json_graph.get("nodes", []):
|
|
266
|
-
if data := node.get("data"):
|
|
267
|
-
|
|
268
|
-
data.pop("id", None)
|
|
269
|
+
if (data := node.get("data")) and isinstance(data, dict):
|
|
270
|
+
data.pop("id", None)
|
|
269
271
|
return ApiResponse(json_graph)
|
|
270
272
|
|
|
271
273
|
try:
|
|
272
274
|
drawable_graph = await graph.aget_graph(xray=xray)
|
|
273
275
|
json_graph = drawable_graph.to_json()
|
|
274
276
|
for node in json_graph.get("nodes", []):
|
|
275
|
-
if data := node.get("data"):
|
|
276
|
-
|
|
277
|
-
data.pop("id", None)
|
|
277
|
+
if (data := node.get("data")) and isinstance(data, dict):
|
|
278
|
+
data.pop("id", None)
|
|
278
279
|
return ApiResponse(json_graph)
|
|
279
280
|
except NotImplementedError:
|
|
280
281
|
raise HTTPException(
|
|
@@ -212,9 +212,11 @@ async def wait_run(request: ApiRequest):
|
|
|
212
212
|
)
|
|
213
213
|
) as stream:
|
|
214
214
|
async for mode, chunk, _ in stream:
|
|
215
|
-
if
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
if (
|
|
216
|
+
mode == b"values"
|
|
217
|
+
or mode == b"updates"
|
|
218
|
+
and b"__interrupt__" in chunk
|
|
219
|
+
):
|
|
218
220
|
vchunk = chunk
|
|
219
221
|
elif mode == b"error":
|
|
220
222
|
vchunk = orjson.dumps({"__error__": orjson.Fragment(chunk)})
|
|
@@ -295,9 +297,11 @@ async def wait_run_stateless(request: ApiRequest):
|
|
|
295
297
|
)
|
|
296
298
|
) as stream:
|
|
297
299
|
async for mode, chunk, _ in stream:
|
|
298
|
-
if
|
|
299
|
-
|
|
300
|
-
|
|
300
|
+
if (
|
|
301
|
+
mode == b"values"
|
|
302
|
+
or mode == b"updates"
|
|
303
|
+
and b"__interrupt__" in chunk
|
|
304
|
+
):
|
|
301
305
|
vchunk = chunk
|
|
302
306
|
elif mode == b"error":
|
|
303
307
|
vchunk = orjson.dumps({"__error__": orjson.Fragment(chunk)})
|
|
@@ -134,6 +134,7 @@ async def list_namespaces(request: ApiRequest):
|
|
|
134
134
|
payload = await request.json(StoreListNamespacesRequest)
|
|
135
135
|
prefix = tuple(payload["prefix"]) if payload.get("prefix") else None
|
|
136
136
|
suffix = tuple(payload["suffix"]) if payload.get("suffix") else None
|
|
137
|
+
err = None
|
|
137
138
|
if prefix and (err := _validate_namespace(prefix)):
|
|
138
139
|
return err
|
|
139
140
|
if suffix and (err := _validate_namespace(suffix)):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import concurrent.futures
|
|
3
3
|
from collections.abc import AsyncIterator, Coroutine
|
|
4
|
-
from contextlib import AbstractAsyncContextManager
|
|
4
|
+
from contextlib import AbstractAsyncContextManager, suppress
|
|
5
5
|
from functools import partial
|
|
6
6
|
from typing import Any, Generic, TypeVar
|
|
7
7
|
|
|
@@ -26,10 +26,8 @@ def get_event_loop() -> asyncio.AbstractEventLoop:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
async def sleep_if_not_done(delay: float, done: asyncio.Event) -> None:
|
|
29
|
-
|
|
29
|
+
with suppress(TimeoutError):
|
|
30
30
|
await asyncio.wait_for(done.wait(), delay)
|
|
31
|
-
except TimeoutError:
|
|
32
|
-
pass
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
class ValueEvent(asyncio.Event):
|
|
@@ -95,14 +93,13 @@ PENDING_TASKS = set()
|
|
|
95
93
|
|
|
96
94
|
|
|
97
95
|
def _create_task_done_callback(
|
|
98
|
-
ignore_exceptions: tuple[Exception, ...],
|
|
96
|
+
ignore_exceptions: tuple[type[Exception], ...],
|
|
99
97
|
task: asyncio.Task | asyncio.Future,
|
|
100
98
|
) -> None:
|
|
101
99
|
PENDING_TASKS.discard(task)
|
|
102
100
|
try:
|
|
103
|
-
if exc := task.exception():
|
|
104
|
-
|
|
105
|
-
logger.exception("asyncio.task failed", exc_info=exc)
|
|
101
|
+
if (exc := task.exception()) and not isinstance(exc, ignore_exceptions):
|
|
102
|
+
logger.exception("asyncio.task failed", exc_info=exc)
|
|
106
103
|
except asyncio.CancelledError:
|
|
107
104
|
pass
|
|
108
105
|
|
|
@@ -176,16 +173,13 @@ class SimpleTaskGroup(AbstractAsyncContextManager["SimpleTaskGroup"]):
|
|
|
176
173
|
self.taskgroup_name = f" {taskgroup_name} " if taskgroup_name else ""
|
|
177
174
|
|
|
178
175
|
def _create_task_done_callback(
|
|
179
|
-
self, ignore_exceptions: tuple[Exception, ...], task: asyncio.Task
|
|
176
|
+
self, ignore_exceptions: tuple[type[Exception], ...], task: asyncio.Task
|
|
180
177
|
) -> None:
|
|
181
|
-
|
|
178
|
+
with suppress(AttributeError):
|
|
182
179
|
self.tasks.remove(task)
|
|
183
|
-
except AttributeError:
|
|
184
|
-
pass
|
|
185
180
|
try:
|
|
186
|
-
if exc := task.exception():
|
|
187
|
-
|
|
188
|
-
logger.exception("asyncio.task failed in task group", exc_info=exc)
|
|
181
|
+
if (exc := task.exception()) and not isinstance(exc, ignore_exceptions):
|
|
182
|
+
logger.exception("asyncio.task failed in task group", exc_info=exc)
|
|
189
183
|
except asyncio.CancelledError:
|
|
190
184
|
pass
|
|
191
185
|
|
|
@@ -286,13 +280,8 @@ class AsyncQueue(Generic[T], asyncio.Queue[T]):
|
|
|
286
280
|
await getter
|
|
287
281
|
except:
|
|
288
282
|
getter.cancel() # Just in case getter is not done yet.
|
|
289
|
-
|
|
290
|
-
# Clean self._getters from canceled getters.
|
|
283
|
+
with suppress(ValueError):
|
|
291
284
|
self._getters.remove(getter)
|
|
292
|
-
except ValueError:
|
|
293
|
-
# The getter could be removed from self._getters by a
|
|
294
|
-
# previous put_nowait call.
|
|
295
|
-
pass
|
|
296
285
|
if not self.empty() and not getter.cancelled():
|
|
297
286
|
# We were woken up by put_nowait(), but can't take
|
|
298
287
|
# the call. Wake up the next in line.
|
|
@@ -239,14 +239,11 @@ def _get_custom_auth_middleware(
|
|
|
239
239
|
|
|
240
240
|
@functools.lru_cache(maxsize=1)
|
|
241
241
|
def _get_auth_instance(path: str | None = None) -> Auth | Literal["js"] | None:
|
|
242
|
-
if path is not None
|
|
243
|
-
auth_instance = _load_auth_obj(path)
|
|
244
|
-
else:
|
|
245
|
-
auth_instance = None
|
|
242
|
+
auth_instance = _load_auth_obj(path) if path is not None else None
|
|
246
243
|
|
|
247
244
|
if auth_instance == "js":
|
|
248
245
|
return auth_instance
|
|
249
|
-
|
|
246
|
+
deps = None
|
|
250
247
|
if auth_instance is not None and (
|
|
251
248
|
deps := _get_dependencies(auth_instance._authenticate_handler)
|
|
252
249
|
):
|
|
@@ -525,6 +522,12 @@ class ProxyUser(BaseUser):
|
|
|
525
522
|
"""Proxy any other attributes to the underlying user object."""
|
|
526
523
|
return getattr(self._user, name)
|
|
527
524
|
|
|
525
|
+
def __iter__(self):
|
|
526
|
+
return iter(self._user)
|
|
527
|
+
|
|
528
|
+
def __len__(self):
|
|
529
|
+
return len(self._user)
|
|
530
|
+
|
|
528
531
|
def __str__(self) -> str:
|
|
529
532
|
return f"{self._user}"
|
|
530
533
|
|
|
@@ -86,9 +86,10 @@ class LangsmithAuthBackend(AuthenticationBackend):
|
|
|
86
86
|
# If tenant id verification is disabled, the bearer token requests
|
|
87
87
|
# are not required to match the tenant id. Api key requests are
|
|
88
88
|
# always required to match the tenant id.
|
|
89
|
-
if
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
if (
|
|
90
|
+
LANGSMITH_AUTH_VERIFY_TENANT_ID or conn.headers.get("x-api-key")
|
|
91
|
+
) and auth_dict["tenant_id"] != LANGSMITH_TENANT_ID:
|
|
92
|
+
raise AuthenticationError("Invalid tenant ID")
|
|
92
93
|
|
|
93
94
|
credentials = AuthCredentials(["authenticated"])
|
|
94
95
|
user = StudioUser(auth_dict.get("user_id"), is_authenticated=True)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections.abc import AsyncGenerator
|
|
2
|
-
from contextlib import asynccontextmanager
|
|
2
|
+
from contextlib import asynccontextmanager, suppress
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
@@ -17,11 +17,10 @@ _client: "JsonHttpClient"
|
|
|
17
17
|
def is_retriable_error(exception: BaseException) -> bool:
|
|
18
18
|
if isinstance(exception, httpx.TransportError):
|
|
19
19
|
return True
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return False
|
|
20
|
+
return (
|
|
21
|
+
isinstance(exception, httpx.HTTPStatusError)
|
|
22
|
+
and exception.response.status_code > 499
|
|
23
|
+
)
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
retry_httpx = retry(
|
|
@@ -106,10 +105,8 @@ def create_client() -> JsonHttpClient:
|
|
|
106
105
|
async def close_auth_client() -> None:
|
|
107
106
|
"""Close the auth http client."""
|
|
108
107
|
global _client
|
|
109
|
-
|
|
108
|
+
with suppress(NameError):
|
|
110
109
|
await _client.client.aclose()
|
|
111
|
-
except NameError:
|
|
112
|
-
pass
|
|
113
110
|
|
|
114
111
|
|
|
115
112
|
async def initialize_auth_client() -> None:
|
|
@@ -132,10 +129,7 @@ async def auth_client() -> AsyncGenerator[JsonHttpClient, None]:
|
|
|
132
129
|
await client.client.aclose()
|
|
133
130
|
else:
|
|
134
131
|
try:
|
|
135
|
-
|
|
136
|
-
found = True
|
|
137
|
-
else:
|
|
138
|
-
found = False
|
|
132
|
+
found = bool(not _client.client.is_closed)
|
|
139
133
|
except NameError:
|
|
140
134
|
found = False
|
|
141
135
|
if found:
|
|
@@ -242,9 +242,12 @@ CORS_CONFIG: CorsConfig | None = env("CORS_CONFIG", cast=_parse_json, default=No
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
"""
|
|
245
|
-
if
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
if (
|
|
246
|
+
CORS_CONFIG is not None
|
|
247
|
+
and CORS_ALLOW_ORIGINS != "*"
|
|
248
|
+
and CORS_CONFIG.get("allow_origins") is None
|
|
249
|
+
):
|
|
250
|
+
CORS_CONFIG["allow_origins"] = CORS_ALLOW_ORIGINS
|
|
248
251
|
|
|
249
252
|
# queue
|
|
250
253
|
|
|
@@ -371,7 +374,7 @@ API_VARIANT = env("LANGSMITH_LANGGRAPH_API_VARIANT", cast=str, default="")
|
|
|
371
374
|
# UI
|
|
372
375
|
UI_USE_BUNDLER = env("LANGGRAPH_UI_BUNDLER", cast=bool, default=False)
|
|
373
376
|
IS_QUEUE_ENTRYPOINT = False
|
|
374
|
-
|
|
377
|
+
ref_sha = None
|
|
375
378
|
if not os.getenv("LANGCHAIN_REVISION_ID") and (
|
|
376
379
|
ref_sha := os.getenv("LANGSMITH_LANGGRAPH_GIT_REF_SHA")
|
|
377
380
|
):
|
|
@@ -46,12 +46,11 @@ async def cron_scheduler():
|
|
|
46
46
|
cron["cron_id"],
|
|
47
47
|
)
|
|
48
48
|
)
|
|
49
|
-
except Exception
|
|
50
|
-
logger.
|
|
49
|
+
except Exception:
|
|
50
|
+
logger.exception(
|
|
51
51
|
"Error scheduling cron run cron_id={}".format(
|
|
52
52
|
cron["cron_id"]
|
|
53
|
-
)
|
|
54
|
-
exc_info=e,
|
|
53
|
+
)
|
|
55
54
|
)
|
|
56
55
|
next_run_date = await run_in_executor(
|
|
57
56
|
None, next_cron_date, cron["schedule"], cron["now"]
|
|
@@ -63,6 +62,6 @@ async def cron_scheduler():
|
|
|
63
62
|
await asyncio.sleep(SLEEP_TIME)
|
|
64
63
|
except asyncio.CancelledError:
|
|
65
64
|
raise
|
|
66
|
-
except Exception
|
|
67
|
-
logger.
|
|
65
|
+
except Exception:
|
|
66
|
+
logger.exception("Error in cron_scheduler")
|
|
68
67
|
await asyncio.sleep(SLEEP_TIME + random())
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
|
|
3
4
|
import httpx
|
|
4
5
|
from tenacity import (
|
|
@@ -65,10 +66,8 @@ class JsonHttpClient:
|
|
|
65
66
|
res.raise_for_status()
|
|
66
67
|
finally:
|
|
67
68
|
# We don't need the response body, so we close the response
|
|
68
|
-
|
|
69
|
+
with contextlib.suppress(UnboundLocalError):
|
|
69
70
|
await res.aclose()
|
|
70
|
-
except UnboundLocalError:
|
|
71
|
-
pass
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
_http_client: JsonHttpClient
|
|
@@ -173,10 +172,7 @@ async def http_request(
|
|
|
173
172
|
|
|
174
173
|
content = None
|
|
175
174
|
if body is not None:
|
|
176
|
-
if isinstance(body, str)
|
|
177
|
-
content = body.encode("utf-8")
|
|
178
|
-
else:
|
|
179
|
-
content = body
|
|
175
|
+
content = body.encode("utf-8") if isinstance(body, str) else body
|
|
180
176
|
elif json is not None:
|
|
181
177
|
content = json_dumpb(json)
|
|
182
178
|
|