langgraph-api 0.2.130__tar.gz → 0.2.134__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.130 → langgraph_api-0.2.134}/Makefile +4 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/PKG-INFO +2 -2
- langgraph_api-0.2.134/benchmark/.gitignore +10 -0
- langgraph_api-0.2.134/benchmark/Makefile +16 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/benchmark/README.md +4 -2
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/benchmark/burst.js +2 -1
- langgraph_api-0.2.134/benchmark/graphs.js +236 -0
- langgraph_api-0.2.134/benchmark/package.json +17 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/benchmark/ramp.js +28 -13
- langgraph_api-0.2.134/langgraph_api/__init__.py +1 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/assistants.py +32 -6
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/meta.py +3 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/openapi.py +1 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/runs.py +50 -10
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/threads.py +27 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/ui.py +2 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/asgi_transport.py +2 -2
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/asyncio.py +10 -8
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/custom.py +9 -4
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/langsmith/client.py +1 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/cli.py +5 -4
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/config.py +1 -1
- langgraph_api-0.2.134/langgraph_api/executor_entrypoint.py +23 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/graph.py +25 -9
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/http.py +10 -7
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/http_metrics.py +4 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/build.mts +11 -2
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/client.http.mts +2 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/client.mts +13 -3
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/package.json +2 -2
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/remote.py +17 -12
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/preload.mjs +9 -1
- langgraph_api-0.2.134/langgraph_api/js/src/utils/files.mts +7 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/sse.py +1 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/yarn.lock +9 -9
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/logging.py +3 -3
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/middleware/http_logger.py +2 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/models/run.py +19 -14
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/patch.py +2 -2
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/queue_entrypoint.py +33 -18
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/schema.py +88 -4
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/serde.py +32 -5
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/server.py +5 -3
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/state.py +8 -8
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/store.py +1 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/stream.py +33 -20
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/traceblock.py +1 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/__init__.py +40 -5
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/config.py +13 -4
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/future.py +1 -1
- langgraph_api-0.2.134/langgraph_api/utils/uuids.py +87 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/validation.py +9 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/webhook.py +20 -20
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/worker.py +8 -5
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/openapi.json +331 -1
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/pyproject.toml +9 -11
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/scripts/create_license.py +4 -3
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/uv.lock +162 -146
- langgraph_api-0.2.130/benchmark/.gitignore +0 -3
- langgraph_api-0.2.130/benchmark/Makefile +0 -9
- langgraph_api-0.2.130/langgraph_api/__init__.py +0 -1
- langgraph_api-0.2.130/langgraph_api/js/src/utils/files.mts +0 -4
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/.gitignore +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/LICENSE +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/README.md +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/benchmark/weather.js +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/constraints.txt +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/forbidden.txt +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/healthcheck.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/__init__.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/mcp.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/store.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/langsmith/backend.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/studio_user.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/command.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/cron_scheduler.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/feature_flags.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/.prettierrc +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/__init__.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/base.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/errors.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/global.d.ts +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/schema.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/graph.mts +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/load.hooks.mjs +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/traceblock.mts +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/tsconfig.json +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/ui.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/metadata.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/middleware/__init__.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/middleware/private_network.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/middleware/request_id.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/models/__init__.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/route.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/thread_ttl.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/tunneling/cloudflare.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/cache.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/headers.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/__init__.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/checkpoint.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/database.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/lifespan.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/metrics.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/ops.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/queue.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/retry.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/store.py +0 -0
- {langgraph_api-0.2.130 → langgraph_api-0.2.134}/logging.json +0 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
lint:
|
|
6
6
|
uv run ruff check .
|
|
7
7
|
uv run ruff format . --diff
|
|
8
|
+
uvx ty check --exclude "**/pb/**" --exclude "**/*_test.py" --exclude "**/test_*.py" --exclude "**/tests/**" --exclude "venv/**" --exclude ".venv/**" --exclude "build/**" --exclude "dist/**" .
|
|
8
9
|
|
|
9
10
|
format:
|
|
10
11
|
uv run ruff check --fix .
|
|
@@ -40,6 +41,8 @@ test-watch-oss:
|
|
|
40
41
|
|
|
41
42
|
test: test-license-oss
|
|
42
43
|
test-watch: test-watch-oss
|
|
44
|
+
unit-test:
|
|
45
|
+
DATABASE_URI="test" REDIS_URI="test" uv run pytest tests/unit_tests
|
|
43
46
|
|
|
44
47
|
test-auth:
|
|
45
48
|
LANGGRAPH_RUNTIME_EDITION=inmem LANGGRAPH_AUTH='{"path": "./tests/graphs/fastapi_jwt_auth.py:get_current_active_user"}' REDIS_URI=_FAKE DATABASE_URI=:memory: MIGRATIONS_PATH=__inmem__ uv run pytest -v $(AUTH_TEST)
|
|
@@ -113,4 +116,4 @@ start-auth-fastapi-jwt:
|
|
|
113
116
|
VERSION_KIND ?= patch
|
|
114
117
|
|
|
115
118
|
bump-version:
|
|
116
|
-
uv run --with hatch hatch version $(VERSION_KIND)
|
|
119
|
+
uv run --with hatch hatch version $(VERSION_KIND)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-api
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.134
|
|
4
4
|
Author-email: Nuno Campos <nuno@langchain.dev>, Will Fu-Hinthorn <will@langchain.dev>
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -11,7 +11,7 @@ Requires-Dist: httpx>=0.25.0
|
|
|
11
11
|
Requires-Dist: jsonschema-rs<0.30,>=0.20.0
|
|
12
12
|
Requires-Dist: langchain-core>=0.3.64
|
|
13
13
|
Requires-Dist: langgraph-checkpoint>=2.0.23
|
|
14
|
-
Requires-Dist: langgraph-runtime-inmem<0.
|
|
14
|
+
Requires-Dist: langgraph-runtime-inmem<0.9.0,>=0.8.0
|
|
15
15
|
Requires-Dist: langgraph-sdk>=0.2.0
|
|
16
16
|
Requires-Dist: langgraph>=0.4.0
|
|
17
17
|
Requires-Dist: langsmith>=0.3.45
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Benchmark commands
|
|
2
|
+
benchmark-burst:
|
|
3
|
+
k6 run burst.js
|
|
4
|
+
|
|
5
|
+
benchmark-ramp:
|
|
6
|
+
k6 run --out json=raw_data_$(shell date +%Y-%m-%dT%H-%M-%S).json ramp.js
|
|
7
|
+
|
|
8
|
+
benchmark-charts:
|
|
9
|
+
npm install
|
|
10
|
+
node graphs.js $(shell ls -t raw_data_*.json | head -1) true
|
|
11
|
+
|
|
12
|
+
benchmark-clean:
|
|
13
|
+
rm -f results_*.json summary_*.json raw_data_*.json *_chart_*.png
|
|
14
|
+
|
|
15
|
+
benchmark-clean-charts:
|
|
16
|
+
rm -f *_chart_*.png
|
|
@@ -12,8 +12,8 @@ There are two modes of testing available:
|
|
|
12
12
|
BURST_SIZE - How many requests to run. Default: 100
|
|
13
13
|
2. `Ramp` - Scale up the number of /run/wait requests and then plateau.
|
|
14
14
|
Available Params:
|
|
15
|
-
LOAD_SIZE - How much traffic to ramp up over a 60s period. Default:
|
|
16
|
-
LEVELS - The number of times to ramp up. Default:
|
|
15
|
+
LOAD_SIZE - How much traffic to ramp up over a 60s period. Default: 500
|
|
16
|
+
LEVELS - The number of times to ramp up. Default: 2
|
|
17
17
|
PLATEAU_DURATION - How long to sustain the max level of traffic in seconds. Default: 300
|
|
18
18
|
|
|
19
19
|
### Agent
|
|
@@ -65,6 +65,8 @@ make benchmark-clean
|
|
|
65
65
|
|
|
66
66
|
Summary results are written to stdout and persisted in a summary_burst file. More detailed results for the same burst are persisted in a results_burst file.
|
|
67
67
|
|
|
68
|
+
Charts can be created from the run locally using the `make benchmark-charts` command.
|
|
69
|
+
|
|
68
70
|
## Resources
|
|
69
71
|
|
|
70
72
|
- [K6 Documentation](https://k6.io/docs/)
|
|
@@ -139,7 +139,8 @@ export default function() {
|
|
|
139
139
|
export function setup() {
|
|
140
140
|
console.log(`Starting burst benchmark`);
|
|
141
141
|
console.log(`Running on pod: ${__ENV.POD_NAME || 'local'}`);
|
|
142
|
-
console.log(`
|
|
142
|
+
console.log(`Running with the following burst config: burst size ${BURST_SIZE}`);
|
|
143
|
+
console.log(`Running with the following agent config: data size ${DATA_SIZE}, delay ${DELAY}, expand ${EXPAND}, mode ${MODE}`);
|
|
143
144
|
|
|
144
145
|
return { startTime: new Date().toISOString() };
|
|
145
146
|
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const QuickChart = require('quickchart-js');
|
|
5
|
+
const { plot } = require('nodeplotlib');
|
|
6
|
+
|
|
7
|
+
// Function to save chart using Quickchart
|
|
8
|
+
async function saveChartWithQuickchart(chartData, chartLayout, filename) {
|
|
9
|
+
const chart = new QuickChart();
|
|
10
|
+
|
|
11
|
+
chart.setWidth(800);
|
|
12
|
+
chart.setHeight(500);
|
|
13
|
+
chart.setFormat('png');
|
|
14
|
+
|
|
15
|
+
const config = {
|
|
16
|
+
type: 'line',
|
|
17
|
+
data: {
|
|
18
|
+
labels: chartData[0].x,
|
|
19
|
+
datasets: chartData.map(metric => {
|
|
20
|
+
return {
|
|
21
|
+
label: metric.name,
|
|
22
|
+
data: metric.y
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
chart.setConfig(config);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const imageBuffer = await chart.toBinary();
|
|
32
|
+
fs.writeFileSync(filename, imageBuffer);
|
|
33
|
+
console.log(`Chart saved to ${filename}`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(`Error saving chart to ${filename}:`, error.message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Function to generate charts from benchmark results
|
|
41
|
+
*
|
|
42
|
+
* Graphs that we generate:
|
|
43
|
+
* - VU scaling over time
|
|
44
|
+
* - Requests over time, broken into success and failure by reason
|
|
45
|
+
* - Average run duration over time
|
|
46
|
+
*
|
|
47
|
+
* Useful but currently in deployment dashboard:
|
|
48
|
+
* - Connection use over time (postgres, redis)
|
|
49
|
+
* - IOPS over time (postgres, redis)
|
|
50
|
+
* - Workers in use over time
|
|
51
|
+
*
|
|
52
|
+
* Dashboard: https://smith.langchain.com/o/ebbaf2eb-769b-4505-aca2-d11de10372a4/host/deployments/a23f03ff-6d4d-4efd-8149-bb5a7f3b95cf?tab=2&paginationModel=%7B%22pageIndex%22%3A0%2C%22pageSize%22%3A10%7D#
|
|
53
|
+
*/
|
|
54
|
+
async function generateCharts(rawDataFile, displayInBrowser = false) {
|
|
55
|
+
console.log("Generating charts for", rawDataFile);
|
|
56
|
+
const aggregatedData = {};
|
|
57
|
+
|
|
58
|
+
// Read the results and summary files
|
|
59
|
+
const fileStream = fs.createReadStream(rawDataFile, 'utf8');
|
|
60
|
+
const rl = readline.createInterface({
|
|
61
|
+
input: fileStream,
|
|
62
|
+
crlfDelay: Infinity
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
for await (const line of rl) {
|
|
66
|
+
const entry = JSON.parse(line);
|
|
67
|
+
|
|
68
|
+
// Always written before the first point for that metric
|
|
69
|
+
if (entry.type === 'Metric') {
|
|
70
|
+
const metricName = entry.metric;
|
|
71
|
+
aggregatedData[metricName] = {};
|
|
72
|
+
} else if (entry.type === 'Point') {
|
|
73
|
+
const metricName = entry.metric;
|
|
74
|
+
const metricValue = entry.data.value;
|
|
75
|
+
const timestamp = Date.parse(entry.data.time);
|
|
76
|
+
|
|
77
|
+
// Round timestamp to 10-second intervals
|
|
78
|
+
const roundedTimestamp = Math.floor(timestamp / 10000) * 10000;
|
|
79
|
+
|
|
80
|
+
if (!aggregatedData[metricName][roundedTimestamp]) {
|
|
81
|
+
aggregatedData[metricName][roundedTimestamp] = [];
|
|
82
|
+
}
|
|
83
|
+
aggregatedData[metricName][roundedTimestamp].push(metricValue);
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error(`Unexpected row type: ${entry.type}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Convert aggregated data to final format with averages
|
|
90
|
+
const finalData = {};
|
|
91
|
+
for (const [metricName, timestampBuckets] of Object.entries(aggregatedData)) {
|
|
92
|
+
finalData[metricName] = [];
|
|
93
|
+
for (const [timestamp, values] of Object.entries(timestampBuckets)) {
|
|
94
|
+
const average = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
95
|
+
const max = Math.max(...values);
|
|
96
|
+
finalData[metricName].push({
|
|
97
|
+
timestamp: timestamp,
|
|
98
|
+
average: average,
|
|
99
|
+
max: max,
|
|
100
|
+
count: values.length
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Sort by timestamp
|
|
104
|
+
finalData[metricName].sort((a, b) => a.timestamp - b.timestamp);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Create the charts
|
|
108
|
+
// We always get a http_reqs metric as one of the first
|
|
109
|
+
const firstTimestamp = finalData['http_reqs'][0].timestamp;
|
|
110
|
+
|
|
111
|
+
// Failed requests won't always be present
|
|
112
|
+
const data = [
|
|
113
|
+
{
|
|
114
|
+
x: finalData['http_reqs'].map(d => (d.timestamp - firstTimestamp) / 1000),
|
|
115
|
+
y: finalData['http_reqs'].map(d => d.count),
|
|
116
|
+
type: 'scatter',
|
|
117
|
+
mode: 'lines',
|
|
118
|
+
name: 'Total Requests',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
x: finalData['successful_runs'].map(d => (d.timestamp - firstTimestamp) / 1000),
|
|
122
|
+
y: finalData['successful_runs'].map(d => d.count),
|
|
123
|
+
type: 'scatter',
|
|
124
|
+
mode: 'lines',
|
|
125
|
+
name: 'Success Rate',
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
if (finalData['failed_runs']) {
|
|
129
|
+
data.push({
|
|
130
|
+
x: finalData['failed_runs'].map(d => (d.timestamp - firstTimestamp) / 1000),
|
|
131
|
+
y: finalData['failed_runs'].map(d => d.count),
|
|
132
|
+
type: 'scatter',
|
|
133
|
+
mode: 'lines',
|
|
134
|
+
name: 'Failed Requests',
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const requestsChart = {
|
|
139
|
+
data,
|
|
140
|
+
layout: {
|
|
141
|
+
title: {
|
|
142
|
+
text: 'Success Rate Over Time',
|
|
143
|
+
},
|
|
144
|
+
xaxis: {
|
|
145
|
+
title: 'Time (10 second intervals)',
|
|
146
|
+
},
|
|
147
|
+
yaxis: {
|
|
148
|
+
title: 'Call Counts',
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const runDurationChart = {
|
|
154
|
+
data: [
|
|
155
|
+
{
|
|
156
|
+
x: finalData['run_duration'].map(d => (d.timestamp - firstTimestamp) / 1000),
|
|
157
|
+
y: finalData['run_duration'].map(d => d.average),
|
|
158
|
+
type: 'scatter',
|
|
159
|
+
mode: 'lines',
|
|
160
|
+
name: 'Run Duration',
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
layout: {
|
|
164
|
+
title: {
|
|
165
|
+
text: 'Run Duration Over Time',
|
|
166
|
+
},
|
|
167
|
+
xaxis: {
|
|
168
|
+
title: 'Time (10 second intervals)',
|
|
169
|
+
},
|
|
170
|
+
yaxis: {
|
|
171
|
+
title: 'Run Duration (ms)',
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const vusChart = {
|
|
177
|
+
data: [
|
|
178
|
+
{
|
|
179
|
+
x: finalData['vus'].map(d => (d.timestamp - firstTimestamp) / 1000),
|
|
180
|
+
y: finalData['vus'].map(d => d.max),
|
|
181
|
+
type: 'scatter',
|
|
182
|
+
mode: 'lines',
|
|
183
|
+
name: 'Concurrent Virtual Users',
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
layout: {
|
|
187
|
+
title: {
|
|
188
|
+
text: 'Concurrent Virtual Users over Time',
|
|
189
|
+
},
|
|
190
|
+
xaxis: {
|
|
191
|
+
title: 'Time (10 second intervals)',
|
|
192
|
+
},
|
|
193
|
+
yaxis: {
|
|
194
|
+
title: 'VUs (max)',
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Always save charts as images using Quickchart
|
|
200
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
201
|
+
const requestsImagePath = path.join(__dirname, `requests_chart_${timestamp}.png`);
|
|
202
|
+
const runDurationImagePath = path.join(__dirname, `run_duration_chart_${timestamp}.png`);
|
|
203
|
+
const vusImagePath = path.join(__dirname, `vus_chart_${timestamp}.png`);
|
|
204
|
+
|
|
205
|
+
// Save charts using Quickchart
|
|
206
|
+
await saveChartWithQuickchart(requestsChart.data, requestsChart.layout, requestsImagePath);
|
|
207
|
+
await saveChartWithQuickchart(runDurationChart.data, runDurationChart.layout, runDurationImagePath);
|
|
208
|
+
await saveChartWithQuickchart(vusChart.data, vusChart.layout, vusImagePath);
|
|
209
|
+
|
|
210
|
+
// Display in CLI if requested
|
|
211
|
+
if (displayInBrowser) {
|
|
212
|
+
plot(requestsChart.data, requestsChart.layout);
|
|
213
|
+
plot(runDurationChart.data, runDurationChart.layout);
|
|
214
|
+
plot(vusChart.data, vusChart.layout);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(`Charts generated: ${vusImagePath}, ${requestsImagePath}, ${runDurationImagePath}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// CLI usage
|
|
221
|
+
if (require.main === module) {
|
|
222
|
+
const [,, rawDataFile, displayInBrowser] = process.argv;
|
|
223
|
+
|
|
224
|
+
if (!rawDataFile) {
|
|
225
|
+
console.error('Usage: node graphs.js <raw-data-file>');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
generateCharts(rawDataFile, displayInBrowser)
|
|
230
|
+
.catch(error => {
|
|
231
|
+
console.error('Failed to generate charts:', error);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = { generateCharts };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "benchmark-charts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Chart generation for K6 benchmark results",
|
|
5
|
+
"main": "graphs.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"generate-charts": "node graphs.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"nodeplotlib": "^1.0.4",
|
|
11
|
+
"quickchart-js": "^3.1.3"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=14.0.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -18,9 +18,10 @@ const BASE_URL = __ENV.BASE_URL || 'http://localhost:9123';
|
|
|
18
18
|
const LANGSMITH_API_KEY = __ENV.LANGSMITH_API_KEY;
|
|
19
19
|
|
|
20
20
|
// Params for the runner
|
|
21
|
-
const LOAD_SIZE = parseInt(__ENV.LOAD_SIZE || '
|
|
22
|
-
const LEVELS = parseInt(__ENV.LEVELS || '
|
|
21
|
+
const LOAD_SIZE = parseInt(__ENV.LOAD_SIZE || '500');
|
|
22
|
+
const LEVELS = parseInt(__ENV.LEVELS || '2');
|
|
23
23
|
const PLATEAU_DURATION = parseInt(__ENV.PLATEAU_DURATION || '300');
|
|
24
|
+
const STATEFUL = __ENV.STATEFUL === 'true';
|
|
24
25
|
|
|
25
26
|
// Params for the agent
|
|
26
27
|
const DATA_SIZE = parseInt(__ENV.DATA_SIZE || '1000');
|
|
@@ -33,7 +34,7 @@ for (let i = 1; i <= LEVELS; i++) {
|
|
|
33
34
|
stages.push({ duration: '60s', target: LOAD_SIZE * i });
|
|
34
35
|
}
|
|
35
36
|
stages.push({ duration: `${PLATEAU_DURATION}s`, target: LOAD_SIZE * LEVELS});
|
|
36
|
-
stages.push({ duration: '
|
|
37
|
+
stages.push({ duration: '60s', target: 0 }); // Ramp down
|
|
37
38
|
|
|
38
39
|
// Test configuration
|
|
39
40
|
export let options = {
|
|
@@ -46,9 +47,10 @@ export let options = {
|
|
|
46
47
|
},
|
|
47
48
|
},
|
|
48
49
|
thresholds: {
|
|
49
|
-
|
|
50
|
-
'
|
|
51
|
-
'
|
|
50
|
+
// These are the first set of goals I'd like to hit. To get the job just working and ramp on main, leaving them off for now.
|
|
51
|
+
// 'run_duration': ['p(95)<10000'], // 95% of runs should complete within 10s
|
|
52
|
+
// 'successful_runs': ['count>100000'], // At least 100,000 successful runs
|
|
53
|
+
// 'http_req_failed': ['rate<0.05'], // Error rate should be less than 5%
|
|
52
54
|
},
|
|
53
55
|
};
|
|
54
56
|
|
|
@@ -75,10 +77,21 @@ export default function() {
|
|
|
75
77
|
config: {
|
|
76
78
|
recursion_limit: EXPAND + 2,
|
|
77
79
|
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// If the request is stateful, create a thread first and use it in the url
|
|
83
|
+
let url = `${BASE_URL}/runs/wait`;
|
|
84
|
+
if (STATEFUL) {
|
|
85
|
+
const thread = http.post(`${BASE_URL}/threads`, payload, {
|
|
86
|
+
headers,
|
|
87
|
+
timeout: '120s' // k6 request timeout slightly longer than the server timeout
|
|
78
88
|
});
|
|
89
|
+
const threadId = thread.json().thread_id;
|
|
90
|
+
url = `${BASE_URL}/threads/${threadId}/runs/wait`;
|
|
91
|
+
}
|
|
79
92
|
|
|
80
93
|
// Make a single request to the wait endpoint
|
|
81
|
-
const response = http.post(
|
|
94
|
+
const response = http.post(url, payload, {
|
|
82
95
|
headers,
|
|
83
96
|
timeout: '120s' // k6 request timeout slightly longer than the server timeout
|
|
84
97
|
});
|
|
@@ -141,9 +154,10 @@ export default function() {
|
|
|
141
154
|
export function setup() {
|
|
142
155
|
console.log(`Starting ramp benchmark`);
|
|
143
156
|
console.log(`Running on pod: ${__ENV.POD_NAME || 'local'}`);
|
|
144
|
-
console.log(`Running ${
|
|
157
|
+
console.log(`Running with the following ramp config: load size ${LOAD_SIZE}, levels ${LEVELS}, plateau duration ${PLATEAU_DURATION}, stateful ${STATEFUL}`);
|
|
158
|
+
console.log(`Running with the following agent config: data size ${DATA_SIZE}, delay ${DELAY}, expand ${EXPAND}, mode ${MODE}`);
|
|
145
159
|
|
|
146
|
-
return { startTime: new Date().toISOString() };
|
|
160
|
+
return { startTime: new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '') };
|
|
147
161
|
}
|
|
148
162
|
|
|
149
163
|
// Handle summary
|
|
@@ -152,13 +166,14 @@ export function handleSummary(data) {
|
|
|
152
166
|
|
|
153
167
|
// Create summary information with aggregated metrics
|
|
154
168
|
const summary = {
|
|
155
|
-
|
|
169
|
+
startTimestamp: data.setup_data.startTime,
|
|
170
|
+
endTimestamp: timestamp,
|
|
156
171
|
metrics: {
|
|
157
|
-
totalRuns: data.metrics.successful_runs.values.count + data.metrics.failed_runs
|
|
172
|
+
totalRuns: data.metrics.successful_runs.values.count + (data.metrics.failed_runs?.values?.count || 0),
|
|
158
173
|
successfulRuns: data.metrics.successful_runs.values.count,
|
|
159
|
-
failedRuns: data.metrics.failed_runs
|
|
174
|
+
failedRuns: data.metrics.failed_runs?.values?.count || 0,
|
|
160
175
|
successRate: data.metrics.successful_runs.values.count /
|
|
161
|
-
(data.metrics.successful_runs.values.count + data.metrics.failed_runs
|
|
176
|
+
(data.metrics.successful_runs.values.count + (data.metrics.failed_runs?.values?.count || 0)) * 100,
|
|
162
177
|
averageDuration: data.metrics.run_duration.values.avg / 1000, // in seconds
|
|
163
178
|
p95Duration: data.metrics.run_duration.values["p(95)"] / 1000, // in seconds
|
|
164
179
|
errors: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.134"
|
|
@@ -16,9 +16,16 @@ from langgraph_api.feature_flags import USE_RUNTIME_CONTEXT_API
|
|
|
16
16
|
from langgraph_api.graph import get_assistant_id, get_graph
|
|
17
17
|
from langgraph_api.js.base import BaseRemotePregel
|
|
18
18
|
from langgraph_api.route import ApiRequest, ApiResponse, ApiRoute
|
|
19
|
+
from langgraph_api.schema import ASSISTANT_FIELDS
|
|
19
20
|
from langgraph_api.serde import ajson_loads
|
|
20
|
-
from langgraph_api.utils import
|
|
21
|
+
from langgraph_api.utils import (
|
|
22
|
+
fetchone,
|
|
23
|
+
get_pagination_headers,
|
|
24
|
+
validate_select_columns,
|
|
25
|
+
validate_uuid,
|
|
26
|
+
)
|
|
21
27
|
from langgraph_api.validation import (
|
|
28
|
+
AssistantCountRequest,
|
|
22
29
|
AssistantCreate,
|
|
23
30
|
AssistantPatch,
|
|
24
31
|
AssistantSearchRequest,
|
|
@@ -61,7 +68,8 @@ def _get_configurable_jsonschema(graph: Pregel) -> dict:
|
|
|
61
68
|
in favor of graph.get_context_jsonschema().
|
|
62
69
|
"""
|
|
63
70
|
# Otherwise, use the config_schema method.
|
|
64
|
-
|
|
71
|
+
# TODO: Remove this when we no longer support langgraph < 0.6
|
|
72
|
+
config_schema = graph.config_schema() # type: ignore[deprecated]
|
|
65
73
|
model_fields = getattr(config_schema, "model_fields", None) or getattr(
|
|
66
74
|
config_schema, "__fields__", None
|
|
67
75
|
)
|
|
@@ -87,11 +95,11 @@ def _state_jsonschema(graph: Pregel) -> dict | None:
|
|
|
87
95
|
for k in graph.stream_channels_list:
|
|
88
96
|
v = graph.channels[k]
|
|
89
97
|
try:
|
|
90
|
-
create_model(k, __root__=(v.UpdateType, None)).
|
|
98
|
+
create_model(k, __root__=(v.UpdateType, None)).model_json_schema()
|
|
91
99
|
fields[k] = (v.UpdateType, None)
|
|
92
100
|
except Exception:
|
|
93
101
|
fields[k] = (Any, None)
|
|
94
|
-
return create_model(graph.get_name("State"), **fields).
|
|
102
|
+
return create_model(graph.get_name("State"), **fields).model_json_schema()
|
|
95
103
|
|
|
96
104
|
|
|
97
105
|
def _graph_schemas(graph: Pregel) -> dict:
|
|
@@ -132,7 +140,7 @@ def _graph_schemas(graph: Pregel) -> dict:
|
|
|
132
140
|
logger.warning(
|
|
133
141
|
f"Failed to get context schema for graph {graph.name} with error: `{str(e)}`"
|
|
134
142
|
)
|
|
135
|
-
context_schema = graph.config_schema()
|
|
143
|
+
context_schema = graph.config_schema() # type: ignore[deprecated]
|
|
136
144
|
else:
|
|
137
145
|
context_schema = None
|
|
138
146
|
|
|
@@ -172,6 +180,7 @@ async def search_assistants(
|
|
|
172
180
|
) -> ApiResponse:
|
|
173
181
|
"""List assistants."""
|
|
174
182
|
payload = await request.json(AssistantSearchRequest)
|
|
183
|
+
select = validate_select_columns(payload.get("select") or None, ASSISTANT_FIELDS)
|
|
175
184
|
offset = int(payload.get("offset") or 0)
|
|
176
185
|
async with connect() as conn:
|
|
177
186
|
assistants_iter, next_offset = await Assistants.search(
|
|
@@ -182,6 +191,7 @@ async def search_assistants(
|
|
|
182
191
|
offset=offset,
|
|
183
192
|
sort_by=payload.get("sort_by"),
|
|
184
193
|
sort_order=payload.get("sort_order"),
|
|
194
|
+
select=select,
|
|
185
195
|
)
|
|
186
196
|
assistants, response_headers = await get_pagination_headers(
|
|
187
197
|
assistants_iter, next_offset, offset
|
|
@@ -189,6 +199,21 @@ async def search_assistants(
|
|
|
189
199
|
return ApiResponse(assistants, headers=response_headers)
|
|
190
200
|
|
|
191
201
|
|
|
202
|
+
@retry_db
|
|
203
|
+
async def count_assistants(
|
|
204
|
+
request: ApiRequest,
|
|
205
|
+
) -> ApiResponse:
|
|
206
|
+
"""Count assistants."""
|
|
207
|
+
payload = await request.json(AssistantCountRequest)
|
|
208
|
+
async with connect() as conn:
|
|
209
|
+
count = await Assistants.count(
|
|
210
|
+
conn,
|
|
211
|
+
graph_id=payload.get("graph_id"),
|
|
212
|
+
metadata=payload.get("metadata"),
|
|
213
|
+
)
|
|
214
|
+
return ApiResponse(count)
|
|
215
|
+
|
|
216
|
+
|
|
192
217
|
@retry_db
|
|
193
218
|
async def get_assistant(
|
|
194
219
|
request: ApiRequest,
|
|
@@ -366,7 +391,7 @@ async def patch_assistant(
|
|
|
366
391
|
|
|
367
392
|
|
|
368
393
|
@retry_db
|
|
369
|
-
async def delete_assistant(request: ApiRequest) ->
|
|
394
|
+
async def delete_assistant(request: ApiRequest) -> Response:
|
|
370
395
|
"""Delete an assistant by ID."""
|
|
371
396
|
assistant_id = request.path_params["assistant_id"]
|
|
372
397
|
validate_uuid(assistant_id, "Invalid assistant ID: must be a UUID")
|
|
@@ -414,6 +439,7 @@ async def set_latest_assistant_version(request: ApiRequest) -> ApiResponse:
|
|
|
414
439
|
assistants_routes: list[BaseRoute] = [
|
|
415
440
|
ApiRoute("/assistants", create_assistant, methods=["POST"]),
|
|
416
441
|
ApiRoute("/assistants/search", search_assistants, methods=["POST"]),
|
|
442
|
+
ApiRoute("/assistants/count", count_assistants, methods=["POST"]),
|
|
417
443
|
ApiRoute(
|
|
418
444
|
"/assistants/{assistant_id}/latest",
|
|
419
445
|
set_latest_assistant_version,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
1
3
|
import langgraph.version
|
|
2
4
|
from starlette.responses import JSONResponse, PlainTextResponse
|
|
3
5
|
|
|
@@ -43,7 +45,7 @@ async def meta_metrics(request: ApiRequest):
|
|
|
43
45
|
|
|
44
46
|
# collect stats
|
|
45
47
|
metrics = get_metrics()
|
|
46
|
-
worker_metrics = metrics["workers"]
|
|
48
|
+
worker_metrics = cast(dict[str, int], metrics["workers"])
|
|
47
49
|
workers_max = worker_metrics["max"]
|
|
48
50
|
workers_active = worker_metrics["active"]
|
|
49
51
|
workers_available = worker_metrics["available"]
|