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.

Files changed (121) hide show
  1. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/Makefile +4 -1
  2. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/PKG-INFO +2 -2
  3. langgraph_api-0.2.134/benchmark/.gitignore +10 -0
  4. langgraph_api-0.2.134/benchmark/Makefile +16 -0
  5. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/benchmark/README.md +4 -2
  6. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/benchmark/burst.js +2 -1
  7. langgraph_api-0.2.134/benchmark/graphs.js +236 -0
  8. langgraph_api-0.2.134/benchmark/package.json +17 -0
  9. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/benchmark/ramp.js +28 -13
  10. langgraph_api-0.2.134/langgraph_api/__init__.py +1 -0
  11. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/assistants.py +32 -6
  12. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/meta.py +3 -1
  13. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/openapi.py +1 -1
  14. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/runs.py +50 -10
  15. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/threads.py +27 -1
  16. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/ui.py +2 -0
  17. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/asgi_transport.py +2 -2
  18. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/asyncio.py +10 -8
  19. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/custom.py +9 -4
  20. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/langsmith/client.py +1 -1
  21. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/cli.py +5 -4
  22. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/config.py +1 -1
  23. langgraph_api-0.2.134/langgraph_api/executor_entrypoint.py +23 -0
  24. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/graph.py +25 -9
  25. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/http.py +10 -7
  26. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/http_metrics.py +4 -1
  27. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/build.mts +11 -2
  28. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/client.http.mts +2 -0
  29. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/client.mts +13 -3
  30. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/package.json +2 -2
  31. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/remote.py +17 -12
  32. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/preload.mjs +9 -1
  33. langgraph_api-0.2.134/langgraph_api/js/src/utils/files.mts +7 -0
  34. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/sse.py +1 -1
  35. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/yarn.lock +9 -9
  36. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/logging.py +3 -3
  37. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/middleware/http_logger.py +2 -1
  38. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/models/run.py +19 -14
  39. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/patch.py +2 -2
  40. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/queue_entrypoint.py +33 -18
  41. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/schema.py +88 -4
  42. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/serde.py +32 -5
  43. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/server.py +5 -3
  44. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/state.py +8 -8
  45. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/store.py +1 -1
  46. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/stream.py +33 -20
  47. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/traceblock.py +1 -1
  48. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/__init__.py +40 -5
  49. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/config.py +13 -4
  50. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/future.py +1 -1
  51. langgraph_api-0.2.134/langgraph_api/utils/uuids.py +87 -0
  52. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/validation.py +9 -0
  53. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/webhook.py +20 -20
  54. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/worker.py +8 -5
  55. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/openapi.json +331 -1
  56. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/pyproject.toml +9 -11
  57. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/scripts/create_license.py +4 -3
  58. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/uv.lock +162 -146
  59. langgraph_api-0.2.130/benchmark/.gitignore +0 -3
  60. langgraph_api-0.2.130/benchmark/Makefile +0 -9
  61. langgraph_api-0.2.130/langgraph_api/__init__.py +0 -1
  62. langgraph_api-0.2.130/langgraph_api/js/src/utils/files.mts +0 -4
  63. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/.gitignore +0 -0
  64. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/LICENSE +0 -0
  65. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/README.md +0 -0
  66. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/benchmark/weather.js +0 -0
  67. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/constraints.txt +0 -0
  68. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/forbidden.txt +0 -0
  69. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/healthcheck.py +0 -0
  70. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/__init__.py +0 -0
  71. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/mcp.py +0 -0
  72. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/api/store.py +0 -0
  73. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/__init__.py +0 -0
  74. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/langsmith/__init__.py +0 -0
  75. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/langsmith/backend.py +0 -0
  76. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/middleware.py +0 -0
  77. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/noop.py +0 -0
  78. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/auth/studio_user.py +0 -0
  79. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/command.py +0 -0
  80. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/cron_scheduler.py +0 -0
  81. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/errors.py +0 -0
  82. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/feature_flags.py +0 -0
  83. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/.gitignore +0 -0
  84. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/.prettierrc +0 -0
  85. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/__init__.py +0 -0
  86. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/base.py +0 -0
  87. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/errors.py +0 -0
  88. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/global.d.ts +0 -0
  89. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/schema.py +0 -0
  90. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/graph.mts +0 -0
  91. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/load.hooks.mjs +0 -0
  92. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/utils/importMap.mts +0 -0
  93. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  94. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/src/utils/serde.mts +0 -0
  95. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/traceblock.mts +0 -0
  96. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/tsconfig.json +0 -0
  97. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/js/ui.py +0 -0
  98. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/metadata.py +0 -0
  99. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/middleware/__init__.py +0 -0
  100. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/middleware/private_network.py +0 -0
  101. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/middleware/request_id.py +0 -0
  102. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/models/__init__.py +0 -0
  103. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/route.py +0 -0
  104. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/sse.py +0 -0
  105. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/thread_ttl.py +0 -0
  106. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/tunneling/cloudflare.py +0 -0
  107. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/cache.py +0 -0
  108. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils/headers.py +0 -0
  109. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_api/utils.py +0 -0
  110. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_license/__init__.py +0 -0
  111. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_license/validation.py +0 -0
  112. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/__init__.py +0 -0
  113. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/checkpoint.py +0 -0
  114. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/database.py +0 -0
  115. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/lifespan.py +0 -0
  116. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/metrics.py +0 -0
  117. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/ops.py +0 -0
  118. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/queue.py +0 -0
  119. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/retry.py +0 -0
  120. {langgraph_api-0.2.130 → langgraph_api-0.2.134}/langgraph_runtime/store.py +0 -0
  121. {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.130
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.7,>=0.6.13
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,10 @@
1
+ # K6 summary and results files
2
+ results_*.json
3
+ summary_*.json
4
+ raw_data_*.json
5
+
6
+ # Generated chart files
7
+ *_chart_*.png
8
+
9
+ # Node.js dependencies
10
+ node_modules/
@@ -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: 100
16
- LEVELS - The number of times to ramp up. Default: 10
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(`Burst size: ${BURST_SIZE}`);
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 || '100');
22
- const LEVELS = parseInt(__ENV.LEVELS || '10');
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: '30s', target: 0 }); // Ramp down
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
- 'run_duration': ['p(95)<30000'], // 95% of runs should complete within 30s
50
- 'successful_runs': ['count>100'], // At least 100 successful runs
51
- 'http_req_failed': ['rate<0.2'], // Error rate should be less than 20%
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(`${BASE_URL}/runs/wait`, payload, {
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 ${LEVELS} levels with base size ${LOAD_SIZE}`);
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
- timestamp: timestamp,
169
+ startTimestamp: data.setup_data.startTime,
170
+ endTimestamp: timestamp,
156
171
  metrics: {
157
- totalRuns: data.metrics.successful_runs.values.count + data.metrics.failed_runs.values.count,
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.values.count,
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.values.count) * 100,
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 fetchone, get_pagination_headers, validate_uuid
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
- config_schema = graph.config_schema()
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)).schema()
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).schema()
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) -> ApiResponse:
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"]
@@ -25,7 +25,7 @@ def set_custom_spec(spec: dict):
25
25
 
26
26
 
27
27
  @lru_cache(maxsize=1)
28
- def get_openapi_spec() -> str:
28
+ def get_openapi_spec() -> bytes:
29
29
  # patch the graph_id enums
30
30
  graph_ids = list(GRAPHS.keys())
31
31
  for schema in (