langgraph-api 0.15.4__tar.gz → 0.16.0__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.
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/Makefile +4 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/PKG-INFO +2 -2
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/README.md +53 -10
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/benchmark_profiles.ts +67 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/benchmarks.ts +3 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/delta-turns-lib.test.js +13 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/delta-turns-lib.ts +10 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/delta_turns.ts +83 -8
- langgraph_api-0.16.0/benchmark/benchmark-runners/interrupt_resume.ts +119 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/types.ts +3 -0
- langgraph_api-0.16.0/benchmark/capacity-ci.js +1025 -0
- langgraph_api-0.16.0/benchmark/capacity-ci.test.js +404 -0
- langgraph_api-0.16.0/benchmark/daily-report.js +503 -0
- langgraph_api-0.16.0/benchmark/daily-report.test.js +463 -0
- langgraph_api-0.16.0/benchmark/delta-report.js +599 -0
- langgraph_api-0.16.0/benchmark/delta-report.test.js +191 -0
- langgraph_api-0.16.0/benchmark/history.js +204 -0
- langgraph_api-0.16.0/benchmark/history.test.js +123 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/host-v2.js +23 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/ramp.js +23 -27
- langgraph_api-0.16.0/benchmark/runner-descriptions.js +58 -0
- langgraph_api-0.16.0/benchmark/server-metrics.js +95 -0
- langgraph_api-0.16.0/benchmark/server-metrics.test.js +63 -0
- langgraph_api-0.16.0/benchmark/slack.js +49 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/staircase.py +59 -2
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/staircase_step_k6.js +28 -3
- langgraph_api-0.16.0/langgraph_api/__init__.py +1 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/__init__.py +53 -8
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/a2a.py +8 -11
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/runs.py +57 -43
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/threads.py +24 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/asyncio.py +30 -5
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/langsmith/backend.py +2 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/studio_user.py +6 -1
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/config/__init__.py +7 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/cron_scheduler.py +16 -1
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/encryption/middleware.py +4 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/errors.py +2 -2
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/graph.py +14 -2
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/ops/crons.py +3 -2
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/ops/runs.py +16 -2
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/ops/threads.py +2 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/client.mts +2 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/package.json +2 -2
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/remote.py +15 -6
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/yarn.lock +66 -126
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/metadata.py +28 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/metrics_otlp.py +25 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/models/run.py +5 -0
- langgraph_api-0.16.0/langgraph_api/runner/__init__.py +10 -0
- langgraph_api-0.16.0/langgraph_api/runner/__main__.py +275 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/schema.py +4 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/self_hosted_logs.py +4 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/serde.py +7 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/state.py +11 -5
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/stream.py +9 -10
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/future.py +1 -1
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/worker.py +17 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/conversion/checkpoint.py +19 -14
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/__init__.py +4 -0
- langgraph_api-0.16.0/langgraph_grpc_common/proto/core_api_pb2.py +276 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/core_api_pb2.pyi +51 -7
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/core_api_pb2_grpc.py +3 -3
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/core_api_pb2_grpc.pyi +3 -3
- langgraph_api-0.16.0/langgraph_grpc_common/proto/runner_pb2.py +50 -0
- langgraph_api-0.16.0/langgraph_grpc_common/proto/runner_pb2.pyi +167 -0
- langgraph_api-0.16.0/langgraph_grpc_common/proto/runner_pb2_grpc.py +108 -0
- langgraph_api-0.16.0/langgraph_grpc_common/proto/runner_pb2_grpc.pyi +73 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/openapi.json +124 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/pyproject.toml +1 -1
- langgraph_api-0.15.4/benchmark/capacity-ci.js +0 -658
- langgraph_api-0.15.4/benchmark/capacity-ci.test.js +0 -138
- langgraph_api-0.15.4/benchmark/daily-report.js +0 -263
- langgraph_api-0.15.4/benchmark/daily-report.test.js +0 -242
- langgraph_api-0.15.4/langgraph_api/__init__.py +0 -1
- langgraph_api-0.15.4/langgraph_grpc_common/proto/core_api_pb2.py +0 -274
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/.gitignore +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/LICENSE +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/README.md +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/.gitignore +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/Makefile +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/assistant.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/benchmark-runner.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/cancel_first_second_completes.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/classify-error.test.js +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/classify-error.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/enqueued_runs_order.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/log-failure.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/meta_workload.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/store.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/stream_write.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/thread.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/thread_runs_metadata_search.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/threads_search_metadata.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/wait_write.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/clean-cli.js +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/clean.js +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/continuous/README.md +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/continuous/pyproject.toml +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/continuous/runner.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/continuous/uv.lock +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/graphs.js +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/host-v2.test.js +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/multer.test.js +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/package.json +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/reporting/dd_reporting.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/tsconfig.json +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/update-revision.js +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/weather.js +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/yarn.lock +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/constraints.txt +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/custom_store.sql +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/forbidden.txt +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/hatch_build.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/healthcheck.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph-cloud-debugging-20260210132856.zip +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/_checkpointer/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/_checkpointer/_adapter.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/_checkpointer/protocol.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/_factory_utils.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/a2ui.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/a2ui_schemas/README.md +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/a2ui_schemas/client_capabilities.json +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/a2ui_schemas/client_data_model.json +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/a2ui_schemas/client_to_server.json +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/a2ui_schemas/server_to_client.json +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/assistants.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/event_streaming.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/mcp/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/mcp/_constants.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/mcp/_handlers.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/mcp/_models.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/mcp/_routes.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/mcp/_sanitizers.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/meta.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/profile.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/store.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/api/ui.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/asgi_transport.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/custom.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/errors.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/langsmith/client.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/beacon_data_plane_jwt_auth.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/cache.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/cli.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/command.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/config/_parse.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/config/schemas.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/encryption/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/encryption/aes_json.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/encryption/context.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/encryption/custom.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/encryption/shared.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/capabilities.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/constants.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/event_normalizers.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/namespace.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/service.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/session.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/state_normalizers.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/event_streaming/types.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/feature_flags.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/client.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/generated/core_api_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/ops/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/ops/assistants.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/ops/cache.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/server.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/servicers/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/servicers/checkpointer.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/grpc/servicers/encryption.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/http.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/http_metrics.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/http_metrics_utils.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/.prettierrc +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/.yarnclean +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/base.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/build.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/client.http.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/errors.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/global.d.ts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/schema.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/auth-mutations.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/graph.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/load.hooks.mjs +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/preload.mjs +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/utils/experiment-tracing.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/utils/files.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/sse.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/traceblock.mts +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/tsconfig.json +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/js/ui.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/lc_security/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/lc_security/exceptions.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/lc_security/policy.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/lc_security/transport.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/logging.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/metrics_collector.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/middleware/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/middleware/ensure_store.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/middleware/http_logger.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/middleware/private_network.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/middleware/request_id.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/models/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/otel_context.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/queue_entrypoint.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/release_tags.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/route.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/self_hosted_metrics.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/server.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/store.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/stream_v2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/timing/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/timing/profiler.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/timing/timer.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/traceblock.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/tracing_session.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/tunneling/cloudflare.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/cache.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/config.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/errors.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/extract.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/headers.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/network.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/retriable_client.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/stream_codec.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/utils/uuids.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/validation.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/webhook.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_api/worker_runtime.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/checkpointer.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/conversion/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/conversion/_compat.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/conversion/config.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/conversion/durability.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/conversion/store.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/conversion/struct.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/conversion/value.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/checkpointer_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/checkpointer_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/checkpointer_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/checkpointer_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/encryption_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/encryption_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/encryption_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/encryption_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/engine_common_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/engine_common_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/engine_common_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/engine_common_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_control_signal_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_control_signal_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_control_signal_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_control_signal_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_durability_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_durability_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_durability_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_durability_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_run_status_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_run_status_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_run_status_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_run_status_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_stream_mode_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_stream_mode_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_stream_mode_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_stream_mode_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_thread_status_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_thread_status_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_thread_status_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_thread_status_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/errors_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/errors_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/errors_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/errors_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/store_pb2.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/store_pb2.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/store_pb2_grpc.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/proto/store_pb2_grpc.pyi +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/serde.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_grpc_common/store.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/__init__.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/checkpoint.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/database.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/lifespan.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/metrics.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/ops.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/queue.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/retry.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/routes.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/langgraph_runtime/store.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/logging.json +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/scripts/build_wheel.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/scripts/create_license.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/scripts/run_a2a_tck.py +0 -0
- {langgraph_api-0.15.4 → langgraph_api-0.16.0}/uv.lock +0 -0
|
@@ -317,6 +317,10 @@ VERSION_KIND ?= patch
|
|
|
317
317
|
bump-version:
|
|
318
318
|
uv run --with hatch hatch version $(VERSION_KIND)
|
|
319
319
|
|
|
320
|
+
sync-runtime-inmem-dep:
|
|
321
|
+
@python3 -c 'import pathlib,re; v=pathlib.Path("../runtime_inmem/langgraph_runtime_inmem/__init__.py").read_text(); m=re.search(r"__version__\s*=\s*\"(\d+)\.(\d+)", v); major,minor=map(int,m.groups()); p=pathlib.Path("pyproject.toml"); text,n=re.subn(r"\"langgraph-runtime-inmem>=[^\"]+\",", f"\"langgraph-runtime-inmem>={major}.{minor}.0.dev0,<{major}.{minor + 1}.0.dev0\",", p.read_text(), count=1); \
|
|
322
|
+
assert n==1, "langgraph-runtime-inmem dep not found in pyproject.toml"; p.write_text(text)'
|
|
323
|
+
|
|
320
324
|
# A2A TCK (Test Compatibility Kit) targets
|
|
321
325
|
# Requires a running server: make start (in another terminal)
|
|
322
326
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
4
4
|
Author-email: Will Fu-Hinthorn <will@langchain.dev>, Josh Rogers <josh@langchain.dev>, Parker Rule <parker@langchain.dev>
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -17,7 +17,7 @@ Requires-Dist: jsonschema-rs<0.45,>=0.20.0
|
|
|
17
17
|
Requires-Dist: langchain-core>=0.3.64
|
|
18
18
|
Requires-Dist: langchain-protocol<0.1,>=0.0.18
|
|
19
19
|
Requires-Dist: langgraph-checkpoint<5,>=3.0.1
|
|
20
|
-
Requires-Dist: langgraph-runtime-inmem<0.
|
|
20
|
+
Requires-Dist: langgraph-runtime-inmem<0.37.0.dev0,>=0.36.0.dev0
|
|
21
21
|
Requires-Dist: langgraph-sdk>=0.4.4
|
|
22
22
|
Requires-Dist: langgraph<2,>=0.4.10
|
|
23
23
|
Requires-Dist: langsmith[otel]>=0.6.3
|
|
@@ -7,7 +7,7 @@ This directory contains k6 + Python/Node benchmark tooling for LangGraph API.
|
|
|
7
7
|
- `make benchmark-ramp` - Ramps up to a set number of concurrent virtual users, holds plateau load, then ramps down.
|
|
8
8
|
- **Environment variables:**
|
|
9
9
|
- `LOAD_SIZE`, `LEVELS`, `PLATEAU_DURATION`
|
|
10
|
-
- `P95_RUN_DURATION
|
|
10
|
+
- `P95_RUN_DURATION`
|
|
11
11
|
- **Example:**
|
|
12
12
|
- `BENCHMARK_PROFILE=streaming-long BENCHMARK_TYPE=stream_write LOAD_SIZE=300 LEVELS=3 make benchmark-ramp`
|
|
13
13
|
|
|
@@ -25,7 +25,7 @@ This directory contains k6 + Python/Node benchmark tooling for LangGraph API.
|
|
|
25
25
|
- `make benchmark-reset` - Cleans benchmark server state via `clean-cli.js`.
|
|
26
26
|
- `make benchmark-new-revision` - Triggers a new benchmark deployment revision.
|
|
27
27
|
|
|
28
|
-
- `make benchmark-capacity-ci` - Runs the daily capacity matrix for one deployment (`DEPLOYMENT` env). Uses `staircase.py` per cell (
|
|
28
|
+
- `make benchmark-capacity-ci` - Runs the daily capacity matrix for one deployment (`DEPLOYMENT` env). Uses `staircase.py` per cell with k6's `constant-arrival-rate` executor (open model: a fixed request rate per step, so capacity reads as the service rate; requests k6 has to drop count as failures). Geometry is per deployment in `capacity-ci.js` (dedicated 50 to 600 req/s in steps of 50, serverless 10 to 70 in steps of 5). `K6_EXECUTOR=constant-vus` restores the closed model. A cell that never meets SLO still reports `throughputCeiling`, the best runs/sec any step served.
|
|
29
29
|
- **Example:**
|
|
30
30
|
- `DEPLOYMENT=daily-cap-benchmark-dedicated make benchmark-capacity-ci`
|
|
31
31
|
|
|
@@ -48,6 +48,7 @@ The runner contains the actual logic each virtual user will execute during the b
|
|
|
48
48
|
- `store` - Writes, reads, validates, and deletes a Store item with a configurable payload size.
|
|
49
49
|
- `meta_workload` (randomly delegates to one of the others)
|
|
50
50
|
- `delta_turns` - Keeps one thread per VU and adds a turn to it every iteration, so latency can be read against thread depth. Built for the delta-channel cells; see below.
|
|
51
|
+
- `interrupt_resume` - Starts a run that pauses on an `interrupt()` before step `interrupt_at`, then resumes it at once. Measures the paused checkpoint write and the full state read on resume. Use with the `interrupt` profile.
|
|
51
52
|
|
|
52
53
|
## Profiles (`BENCHMARK_PROFILE`)
|
|
53
54
|
|
|
@@ -60,6 +61,9 @@ Profiles are quick ways to configure the benchmark graph for any run depending o
|
|
|
60
61
|
- `parallel-small` - Stateful high-fanout profile with small checkpoint payloads.
|
|
61
62
|
- `parallel-medium` - Stateful high-fanout profile with medium checkpoint payloads.
|
|
62
63
|
- `delta-turns` - Cheap turns for the delta-channel cells, so a VU reaches the depth where a `snapshot_frequency` of 1000 fires.
|
|
64
|
+
- `wide-deep` - 50 parallel branches held for 20 supersteps: concurrent checkpoint writes and worker spread at the same time.
|
|
65
|
+
- `messages` - State grows as a chat message list (2 KB per step, 10 steps), so each checkpoint serializes structured objects rather than appending bytes.
|
|
66
|
+
- `interrupt` - 5 steps, pauses before step 3. For the `interrupt_resume` runner.
|
|
63
67
|
|
|
64
68
|
Each profile defines:
|
|
65
69
|
|
|
@@ -85,6 +89,9 @@ Context overrides (merged on top of profile):
|
|
|
85
89
|
- `EXPAND` - Fanout width (number of parallel branches at start).
|
|
86
90
|
- `STEPS` - Number of graph turns/iterations to execute.
|
|
87
91
|
- `CHECKPOINT_SIZE` - Bytes written per checkpoint operation.
|
|
92
|
+
- `PAYLOAD` - `text` (default; compresses like real state), `random` (incompressible worst case) or `messages` (a chat message of `CHECKPOINT_SIZE` chars per step).
|
|
93
|
+
- `CHANNELS` - Spread the bytes payload across this many channels (1 to 8). Postgres stores one row per changed channel per version, so this costs rows, not only bytes.
|
|
94
|
+
- `INTERRUPT_AT` - Pause the run before this step (1-based) until resumed. 0 never pauses.
|
|
88
95
|
- `STORE_VALUE_SIZE` - Store runner payload size in bytes (defaults to 1 MiB).
|
|
89
96
|
- `LLM_ENABLED` - Enables fake LLM streaming inside the benchmark graph.
|
|
90
97
|
- `STREAM_SIZE` - Normal total streamed payload size (bytes).
|
|
@@ -122,7 +129,11 @@ by replaying them. That moves cost from write to read, and the read cost grows w
|
|
|
122
129
|
thread depth. `delta_turns` measures it: one thread per VU, one turn per iteration,
|
|
123
130
|
`turn_duration` tagged by depth bucket.
|
|
124
131
|
|
|
125
|
-
|
|
132
|
+
`benchmark-delta.yml` runs four cells nightly, 20 users for 25 minutes each, 1 KB appended per turn (`CHECKPOINT_SIZE`): plain channel, then
|
|
133
|
+
delta snapshotting every 50 (`benchmark_delta_freq`, the deepagents setting), 500
|
|
134
|
+
(`benchmark_delta_500`) and 1000 (`benchmark_delta`, the library default). `delta-report.js` posts
|
|
135
|
+
a headline table (non-delta vs delta at 50: read, write, storage by depth) and one table per metric
|
|
136
|
+
across the three frequencies. The messages and migration cells stay manual. Run the cells one at a time and compare `turn_duration` by depth. Both sides get slower
|
|
126
137
|
with depth for different reasons, the control by re-writing a growing value and delta by
|
|
127
138
|
replaying more writes, so the deliverable is which curve is worse and where they cross.
|
|
128
139
|
|
|
@@ -131,12 +142,14 @@ replaying more writes, so the deliverable is which curve is worse and where they
|
|
|
131
142
|
| control | `GRAPH_ID=benchmark EXPECT_DELTA=false` |
|
|
132
143
|
| delta, snapshot every 1000 | `GRAPH_ID=benchmark_delta` |
|
|
133
144
|
| delta, snapshot every 50 | `GRAPH_ID=benchmark_delta_freq` |
|
|
145
|
+
| delta, snapshot every 500 | `GRAPH_ID=benchmark_delta_500` |
|
|
146
|
+
| delta on a message list | `GRAPH_ID=benchmark_delta_messages PAYLOAD=messages` |
|
|
134
147
|
| migration | `GRAPH_ID=benchmark_delta MIGRATE_FROM_GRAPH_ID=benchmark MIGRATE_AFTER=200` |
|
|
135
148
|
|
|
136
149
|
```bash
|
|
137
150
|
BASE_URL=... BENCHMARK_TYPE=delta_turns BENCHMARK_PROFILE=delta-turns \
|
|
138
151
|
GRAPH_ID=benchmark_delta LOAD_SIZE=25 LEVELS=1 PLATEAU_DURATION=300 \
|
|
139
|
-
P95_RUN_DURATION=...
|
|
152
|
+
P95_RUN_DURATION=... k6 run --system-tags='[]' ramp.js
|
|
140
153
|
```
|
|
141
154
|
|
|
142
155
|
Knobs beyond the common ones:
|
|
@@ -146,21 +159,23 @@ Knobs beyond the common ones:
|
|
|
146
159
|
- `MIGRATE_FROM_GRAPH_ID` / `MIGRATE_AFTER` - run the first N turns on another graph, then
|
|
147
160
|
switch on the same thread.
|
|
148
161
|
- `VERIFY_EVERY` - cold re-read cadence in turns. Default 50, `0` disables.
|
|
149
|
-
- `DEPTH_BUCKET` - width of a depth bucket.
|
|
162
|
+
- `DEPTH_BUCKET` - width of a depth bucket. Defaults to `VERIFY_EVERY`, so each bucket holds one cold read per thread.
|
|
150
163
|
|
|
151
164
|
Two things to know before reading any result:
|
|
152
165
|
|
|
153
166
|
- **Run at low `LOAD_SIZE`.** Depth and load fight each other: saturate the deployment and
|
|
154
167
|
turn time measures queueing, not reconstruction. Verify the control curve is flat before
|
|
155
168
|
trusting the delta one.
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
and treat these cells as report-only until the numbers settle.
|
|
169
|
+
- **The ramp gates on p95 and error rate only.** Pass a `P95_RUN_DURATION` that fits the
|
|
170
|
+
cell, and treat these cells as report-only until the numbers settle.
|
|
159
171
|
|
|
160
172
|
The runner fails a turn rather than reporting a false pass when delta is not actually on:
|
|
161
173
|
the `DeltaChannel` import is version-guarded, and its stub degrades to a plain channel.
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
At every `VERIFY_EVERY` turn it also times the cold `GET /threads/{thread_id}/state` as
|
|
175
|
+
`state_read_duration` (the read path alone; a turn is one save plus the read before it), and
|
|
176
|
+
reads `GET /threads/{thread_id}/storage` and records
|
|
177
|
+
`thread_storage_bytes` tagged by depth bucket and graph, so the control and delta cells can
|
|
178
|
+
be compared on the bytes they occupy, which is what the feature exists to reduce.
|
|
164
179
|
|
|
165
180
|
## Outputs
|
|
166
181
|
|
|
@@ -177,6 +192,12 @@ against the deployment database, which CI cannot reach.
|
|
|
177
192
|
|
|
178
193
|
- Daily ramp benchmark: `.github/workflows/benchmark.yml`
|
|
179
194
|
- Daily capacity matrix: `.github/workflows/benchmark-capacity-matrix.yml`
|
|
195
|
+
- Daily delta-channel cells: `.github/workflows/benchmark-delta.yml`, reported by `delta-report.js`
|
|
196
|
+
|
|
197
|
+
The staging deployments build their graphs from
|
|
198
|
+
[langgraph-server-benchmarks](https://github.com/langchain-ai/langgraph-server-benchmarks), not
|
|
199
|
+
from this repo. Its `src/agent/graph.py` is a copy of `api/tests/graphs/benchmark.py`; a graph
|
|
200
|
+
change here reaches the nightly only once that copy is updated.
|
|
180
201
|
|
|
181
202
|
### Capacity matrix setup
|
|
182
203
|
|
|
@@ -184,6 +205,19 @@ against the deployment database, which CI cannot reach.
|
|
|
184
205
|
2. Fill in `deploymentId` and `baseUrl` in `CAPACITY_DEPLOYMENTS` at the top of [`capacity-ci.js`](capacity-ci.js).
|
|
185
206
|
3. Scheduled run: two parallel CI jobs (one per deployment), aggregate job posts Slack summary.
|
|
186
207
|
|
|
208
|
+
Each cell also records the deployment's own view of its window (queue and API pod counts, CPU peaks) from the control plane's deployment metrics route. The posted message names any component at or over 95% of its CPU request as that tier's bottleneck, and the full per-cell table goes to the message's thread. Every tier starts at one queue pod and autoscales during a cell, so a capacity number is partly a pod-count number; pinning pods at the tier maximum would need the benchmark deployments recreated as `external_docker` sources, the only kind whose `resource_spec` the API accepts.
|
|
209
|
+
|
|
210
|
+
### Slack reports
|
|
211
|
+
|
|
212
|
+
Every report follows one shape so the channel reads the same way each morning, on a phone as well as a desktop:
|
|
213
|
+
|
|
214
|
+
- The first line is the verdict: a dot (green quiet, yellow something regressed or a bottleneck or errors, red something failed), the report name and date, `manual run` when dispatched by hand, the API version, and a clause saying what needs attention. The second line says what the benchmark is, for a reader outside the team.
|
|
215
|
+
- Then `Failed`, `Regressed`, `Improved` and `Bottlenecks` or `Errors` lines, worst first. `Regressed` prints `none` rather than going quiet.
|
|
216
|
+
- One table per deployment, about 40 characters wide so it fits a phone, with `▲ better` / `▼ worse` markers on cells that left their usual range.
|
|
217
|
+
- One legend line (pass rule, arrows, dot colors), then a link to the thread, which holds the per-cell or per-run numbers and the longer *How to read*, and to the run's artifacts.
|
|
218
|
+
|
|
219
|
+
Both reports compare tonight with the previous scheduled nights (`history.js`, artifacts fetched with `gh`). A figure is flagged only when it leaves that cell's own observed range, and only once five same-kind nights exist; before that the table says how many nights it has. The daily benchmark posts one digest per workflow once its last runner finishes (`daily-report.js`, fed by each runner's artifact), not one message per runner. The delta-channel report (`delta-report.js`) keeps the plain-vs-delta table in the channel and puts the snapshot-frequency sweep in the thread.
|
|
220
|
+
|
|
187
221
|
Local commands:
|
|
188
222
|
|
|
189
223
|
```bash
|
|
@@ -201,3 +235,12 @@ node capacity-ci.js --aggregate --summaries-dir ./artifacts --slack
|
|
|
201
235
|
|
|
202
236
|
- [k6 docs](https://k6.io/docs/)
|
|
203
237
|
- [k6 JavaScript API](https://k6.io/docs/javascript-api/)
|
|
238
|
+
|
|
239
|
+
## What a run returns
|
|
240
|
+
|
|
241
|
+
The graph's output schema is `counter`, `start_time`, `end_time`, `payload_bytes` and
|
|
242
|
+
`payload_sha256`. The payload channels themselves stay out of `/runs/wait` bodies and
|
|
243
|
+
`values` events, so `CHECKPOINT_SIZE` drives database writes only; before this, every
|
|
244
|
+
response carried the whole payload base64-encoded and write cost could not be told from
|
|
245
|
+
egress. `GET /threads/{thread_id}/state` still returns every channel. The delta cells
|
|
246
|
+
verify a cold re-read by hashing it and comparing with `payload_sha256`.
|
{langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/benchmark_profiles.ts
RENAMED
|
@@ -13,6 +13,9 @@ export interface BenchmarkContext {
|
|
|
13
13
|
burst_mode: boolean;
|
|
14
14
|
burst_probability: number;
|
|
15
15
|
burst_size: number;
|
|
16
|
+
payload?: 'text' | 'random' | 'messages';
|
|
17
|
+
channels?: number;
|
|
18
|
+
interrupt_at?: number;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export interface BenchmarkProfileEnv {
|
|
@@ -32,6 +35,9 @@ export interface BenchmarkProfileEnv {
|
|
|
32
35
|
BURST_MODE?: string;
|
|
33
36
|
BURST_PROBABILITY?: string;
|
|
34
37
|
BURST_SIZE?: string;
|
|
38
|
+
PAYLOAD?: string;
|
|
39
|
+
CHANNELS?: string;
|
|
40
|
+
INTERRUPT_AT?: string;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
export interface BenchmarkProfile {
|
|
@@ -173,6 +179,64 @@ export const BENCHMARK_PROFILES: Record<string, BenchmarkProfile> = {
|
|
|
173
179
|
burst_size: 0,
|
|
174
180
|
},
|
|
175
181
|
},
|
|
182
|
+
// Width held for many supersteps: concurrent checkpoint writes and worker
|
|
183
|
+
// spread at the same time, which the wide-but-one-step profiles never do.
|
|
184
|
+
'wide-deep': {
|
|
185
|
+
resumable: false,
|
|
186
|
+
runMode: 'stateless',
|
|
187
|
+
context: {
|
|
188
|
+
delay: 0,
|
|
189
|
+
delay_jitter_ratio: 0.2,
|
|
190
|
+
expand: 50,
|
|
191
|
+
steps: 20,
|
|
192
|
+
checkpoint_size: 1_000,
|
|
193
|
+
llm_enabled: false,
|
|
194
|
+
stream_size: 0,
|
|
195
|
+
chunk_size: 1,
|
|
196
|
+
burst_mode: false,
|
|
197
|
+
burst_probability: 0,
|
|
198
|
+
burst_size: 0,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
// State grows as a message list, so each checkpoint serializes structured
|
|
202
|
+
// objects rather than appending bytes.
|
|
203
|
+
messages: {
|
|
204
|
+
resumable: false,
|
|
205
|
+
runMode: 'stateful',
|
|
206
|
+
context: {
|
|
207
|
+
delay: 0,
|
|
208
|
+
delay_jitter_ratio: 0.2,
|
|
209
|
+
expand: 1,
|
|
210
|
+
steps: 10,
|
|
211
|
+
checkpoint_size: 2_000,
|
|
212
|
+
payload: 'messages',
|
|
213
|
+
llm_enabled: false,
|
|
214
|
+
stream_size: 0,
|
|
215
|
+
chunk_size: 1,
|
|
216
|
+
burst_mode: false,
|
|
217
|
+
burst_probability: 0,
|
|
218
|
+
burst_size: 0,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
// For the interrupt_resume runner.
|
|
222
|
+
interrupt: {
|
|
223
|
+
resumable: false,
|
|
224
|
+
runMode: 'stateful',
|
|
225
|
+
context: {
|
|
226
|
+
delay: 0,
|
|
227
|
+
delay_jitter_ratio: 0.2,
|
|
228
|
+
expand: 1,
|
|
229
|
+
steps: 5,
|
|
230
|
+
checkpoint_size: 1_000,
|
|
231
|
+
interrupt_at: 3,
|
|
232
|
+
llm_enabled: false,
|
|
233
|
+
stream_size: 0,
|
|
234
|
+
chunk_size: 1,
|
|
235
|
+
burst_mode: false,
|
|
236
|
+
burst_probability: 0,
|
|
237
|
+
burst_size: 0,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
176
240
|
};
|
|
177
241
|
|
|
178
242
|
export function getBenchmarkProfile(profileName?: string): { name: string; profile: BenchmarkProfile } {
|
|
@@ -241,6 +305,9 @@ export function get_profile(env: BenchmarkProfileEnv): ParsedBenchmarkProfile {
|
|
|
241
305
|
? { burst_probability: parseOptionalFloat(env.BURST_PROBABILITY)! }
|
|
242
306
|
: {}),
|
|
243
307
|
...(parseOptionalInt(env.BURST_SIZE) !== undefined ? { burst_size: parseOptionalInt(env.BURST_SIZE)! } : {}),
|
|
308
|
+
...(env.PAYLOAD ? { payload: env.PAYLOAD as 'text' | 'random' | 'messages' } : {}),
|
|
309
|
+
...(parseOptionalInt(env.CHANNELS) !== undefined ? { channels: parseOptionalInt(env.CHANNELS)! } : {}),
|
|
310
|
+
...(parseOptionalInt(env.INTERRUPT_AT) !== undefined ? { interrupt_at: parseOptionalInt(env.INTERRUPT_AT)! } : {}),
|
|
244
311
|
});
|
|
245
312
|
|
|
246
313
|
return {
|
|
@@ -9,6 +9,7 @@ import { ThreadsSearchMetadata } from './threads_search_metadata.js';
|
|
|
9
9
|
import { Store } from './store.js';
|
|
10
10
|
import { MetaWorkload } from './meta_workload.js';
|
|
11
11
|
import { DeltaTurns } from './delta_turns.js';
|
|
12
|
+
import { InterruptResume } from './interrupt_resume.js';
|
|
12
13
|
import type { BenchmarkRunner } from './benchmark-runner.js';
|
|
13
14
|
|
|
14
15
|
export class Benchmarks {
|
|
@@ -36,6 +37,8 @@ export class Benchmarks {
|
|
|
36
37
|
return MetaWorkload;
|
|
37
38
|
case DeltaTurns.toString():
|
|
38
39
|
return DeltaTurns;
|
|
40
|
+
case InterruptResume.toString():
|
|
41
|
+
return InterruptResume;
|
|
39
42
|
default:
|
|
40
43
|
throw new Error(`Unknown benchmark type: ${type}`);
|
|
41
44
|
}
|
{langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/delta-turns-lib.test.js
RENAMED
|
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import {
|
|
4
4
|
planTurn,
|
|
5
5
|
deltaGuardFailure,
|
|
6
|
+
digestDrift,
|
|
6
7
|
stateDrift,
|
|
7
8
|
unusableCheckpointSize,
|
|
8
9
|
} from './dist/delta-turns-lib.js';
|
|
@@ -167,3 +168,15 @@ test('the delta-turns profile is wired and writes a growing channel', () => {
|
|
|
167
168
|
assert.equal(profile.context.delay, 0);
|
|
168
169
|
assert.equal(profile.context.steps, 1);
|
|
169
170
|
});
|
|
171
|
+
|
|
172
|
+
test('matching digests are no drift, and a changed byte anywhere is', () => {
|
|
173
|
+
const live = 'a'.repeat(64);
|
|
174
|
+
assert.equal(digestDrift(live, live), null);
|
|
175
|
+
assert.equal(digestDrift(live, 'b'.repeat(64)), `digest_drift:${'a'.repeat(12)}!=${'b'.repeat(12)}`);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('a missing digest on either side is reported, not passed', () => {
|
|
179
|
+
assert.equal(digestDrift(undefined, 'a'.repeat(64)), 'run_digest_missing');
|
|
180
|
+
assert.equal(digestDrift('', 'a'.repeat(64)), 'run_digest_missing');
|
|
181
|
+
assert.equal(digestDrift('a'.repeat(64), null), 'cold_digest_missing');
|
|
182
|
+
});
|
{langgraph_api-0.15.4 → langgraph_api-0.16.0}/benchmark/benchmark-runners/delta-turns-lib.ts
RENAMED
|
@@ -100,3 +100,13 @@ function encodeChannel(value: unknown): string {
|
|
|
100
100
|
if (value == null) return '';
|
|
101
101
|
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The run reports the SHA-256 of its stored payload; the cold read is hashed
|
|
106
|
+
* the same way. A rebuild that loses or corrupts a write changes the hash.
|
|
107
|
+
*/
|
|
108
|
+
export function digestDrift(liveDigest: unknown, coldDigest: unknown): string | null {
|
|
109
|
+
if (typeof liveDigest !== 'string' || liveDigest === '') return 'run_digest_missing';
|
|
110
|
+
if (typeof coldDigest !== 'string' || coldDigest === '') return 'cold_digest_missing';
|
|
111
|
+
return liveDigest === coldDigest ? null : `digest_drift:${liveDigest.slice(0, 12)}!=${coldDigest.slice(0, 12)}`;
|
|
112
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { BenchmarkRunner } from './benchmark-runner.js';
|
|
2
2
|
import type { ErrorMetrics } from './benchmark-runner.js';
|
|
3
3
|
import { check } from 'k6';
|
|
4
|
+
import crypto from 'k6/crypto';
|
|
5
|
+
import encoding from 'k6/encoding';
|
|
4
6
|
import http from 'k6/http';
|
|
5
7
|
import { Trend } from 'k6/metrics';
|
|
6
8
|
import type { BenchmarkResult, BenchmarkGraphOptions, HttpResponse } from './types.js';
|
|
@@ -10,22 +12,36 @@ import { logFailure } from './log-failure.js';
|
|
|
10
12
|
import {
|
|
11
13
|
planTurn,
|
|
12
14
|
deltaGuardFailure,
|
|
13
|
-
|
|
15
|
+
digestDrift,
|
|
14
16
|
unusableCheckpointSize,
|
|
15
17
|
} from './delta-turns-lib.js';
|
|
16
18
|
import type { DeltaTurnsOptions, TurnPlan } from './delta-turns-lib.js';
|
|
17
19
|
|
|
18
|
-
const
|
|
20
|
+
const PAYLOAD_CHANNELS = [
|
|
21
|
+
'cumulative_checkpoint',
|
|
22
|
+
'payload_2',
|
|
23
|
+
'payload_3',
|
|
24
|
+
'payload_4',
|
|
25
|
+
'payload_5',
|
|
26
|
+
'payload_6',
|
|
27
|
+
'payload_7',
|
|
28
|
+
'payload_8',
|
|
29
|
+
] as const;
|
|
19
30
|
|
|
20
31
|
/** Tagged by depth: VUs ramp in, so clock time is not thread length. */
|
|
21
32
|
const turnDuration = new Trend('turn_duration', true);
|
|
33
|
+
/** The read path alone: a cold GET /state replays the channel without writing. */
|
|
34
|
+
const stateReadDuration = new Trend('state_read_duration', true);
|
|
35
|
+
const checkpointSaveDuration = new Trend('checkpoint_save', true);
|
|
36
|
+
const threadStorageBytes = new Trend('thread_storage_bytes', false);
|
|
22
37
|
|
|
23
38
|
const TURN_CONFIG: Omit<DeltaTurnsOptions, 'graphId'> = {
|
|
24
39
|
migrateFromGraphId: __ENV.MIGRATE_FROM_GRAPH_ID || '',
|
|
25
40
|
migrateAfter: parseInt(__ENV.MIGRATE_AFTER || '0', 10),
|
|
26
41
|
expectDelta: (__ENV.EXPECT_DELTA || 'true') === 'true',
|
|
27
42
|
verifyEvery: parseInt(__ENV.VERIFY_EVERY || '50', 10),
|
|
28
|
-
|
|
43
|
+
// One bucket per cold read, so every row of the report is one sample per thread.
|
|
44
|
+
bucketSize: parseInt(__ENV.DEPTH_BUCKET || __ENV.VERIFY_EVERY || '50', 10),
|
|
29
45
|
};
|
|
30
46
|
|
|
31
47
|
// Each k6 VU runs this module in its own context, so these are per VU.
|
|
@@ -37,6 +53,8 @@ interface DeltaTurnsData {
|
|
|
37
53
|
plan: TurnPlan;
|
|
38
54
|
guardFailure: string | null;
|
|
39
55
|
drift: string | null;
|
|
56
|
+
/** Non-200 status of the cold state read, which is a failed request, not a correctness failure. */
|
|
57
|
+
stateStatus: number | null;
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
export class DeltaTurns extends BenchmarkRunner {
|
|
@@ -68,6 +86,9 @@ export class DeltaTurns extends BenchmarkRunner {
|
|
|
68
86
|
input: {},
|
|
69
87
|
context: { ...benchmarkGraphOptions.context, steps: plan.steps },
|
|
70
88
|
config: { recursion_limit: plan.steps + 2 },
|
|
89
|
+
// The graph times its own checkpoint save as the gap between its two
|
|
90
|
+
// steps; a background save would overlap the second step and hide in it.
|
|
91
|
+
durability: 'sync',
|
|
71
92
|
});
|
|
72
93
|
|
|
73
94
|
const startedAt = Date.now();
|
|
@@ -78,16 +99,34 @@ export class DeltaTurns extends BenchmarkRunner {
|
|
|
78
99
|
);
|
|
79
100
|
turnDuration.add(Date.now() - startedAt, { depth: plan.depthBucket });
|
|
80
101
|
addResponse(responses, 'wait', response);
|
|
102
|
+
const saveMs = response.status === 200 ? safeJson(response.body)?.save_ms : null;
|
|
103
|
+
if (typeof saveMs === 'number' && saveMs > 0) {
|
|
104
|
+
// Snapshots land on multiples of the bucket width, so the bucket's last
|
|
105
|
+
// turn is where a snapshot save shows; a percentile over the bucket hides it.
|
|
106
|
+
const rowTurn = plan.steps % TURN_CONFIG.bucketSize === 0 ? '1' : '0';
|
|
107
|
+
checkpointSaveDuration.add(saveMs, { depth: plan.depthBucket, row_turn: rowTurn });
|
|
108
|
+
}
|
|
81
109
|
|
|
82
110
|
let guardFailure: string | null = null;
|
|
83
111
|
let drift: string | null = null;
|
|
112
|
+
let stateStatus: number | null = null;
|
|
84
113
|
// A failed body is not state, and comparing against it would report drift
|
|
85
114
|
// on top of the failure that caused it.
|
|
86
115
|
if (response.status === 200 && (plan.guard || plan.verify)) {
|
|
87
116
|
const state = http.get(`${baseUrl}/threads/${threadId}/state`, requestParams);
|
|
117
|
+
// Time to first byte: the server cannot start answering before it has
|
|
118
|
+
// rebuilt the value, and the body that follows is the whole state, which
|
|
119
|
+
// at depth is hundreds of KB the turn itself never sends. Wall time made
|
|
120
|
+
// a plain-channel read cost as much as a whole turn. The turn-2 guard
|
|
121
|
+
// read is at depth 2, not the bucket's end, so it is not timed.
|
|
122
|
+
if (state.status === 200 && plan.verify) {
|
|
123
|
+
stateReadDuration.add(state.timings.waiting, { depth: plan.depthBucket });
|
|
124
|
+
}
|
|
88
125
|
addResponse(responses, 'state', state);
|
|
89
126
|
const body = state.status === 200 ? safeJson(state.body) : null;
|
|
90
|
-
if (
|
|
127
|
+
if (state.status !== 200) {
|
|
128
|
+
stateStatus = state.status;
|
|
129
|
+
} else if (body === null) {
|
|
91
130
|
guardFailure = plan.guard ? 'state_unreadable' : guardFailure;
|
|
92
131
|
drift = plan.verify ? 'state_unreadable' : drift;
|
|
93
132
|
} else {
|
|
@@ -95,13 +134,26 @@ export class DeltaTurns extends BenchmarkRunner {
|
|
|
95
134
|
guardFailure = deltaGuardFailure(body.metadata, plan.expectDelta);
|
|
96
135
|
}
|
|
97
136
|
if (plan.verify) {
|
|
98
|
-
|
|
99
|
-
|
|
137
|
+
drift = digestDrift(safeJson(response.body)?.payload_sha256, coldDigest(body.values));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (plan.verify) {
|
|
141
|
+
// Servers before 0.16 have no storage endpoint. A 404 there is a missing
|
|
142
|
+
// sample, not a failed request: without this, one 404 per 50 turns is
|
|
143
|
+
// about 2% and trips the 1% error budget on every cell.
|
|
144
|
+
const storage = http.get(`${baseUrl}/threads/${threadId}/storage`, {
|
|
145
|
+
...requestParams,
|
|
146
|
+
responseCallback: http.expectedStatuses(200, 404),
|
|
147
|
+
});
|
|
148
|
+
addResponse(responses, 'storage', storage);
|
|
149
|
+
const total = storage.status === 200 ? safeJson(storage.body)?.total_bytes : null;
|
|
150
|
+
if (typeof total === 'number') {
|
|
151
|
+
threadStorageBytes.add(total, { depth: plan.depthBucket, graph: plan.graphId });
|
|
100
152
|
}
|
|
101
153
|
}
|
|
102
154
|
}
|
|
103
155
|
|
|
104
|
-
return okResult(responses, { rawResponse: response, plan, guardFailure, drift });
|
|
156
|
+
return okResult(responses, { rawResponse: response, plan, guardFailure, drift, stateStatus });
|
|
105
157
|
}
|
|
106
158
|
|
|
107
159
|
static validate(
|
|
@@ -120,6 +172,7 @@ export class DeltaTurns extends BenchmarkRunner {
|
|
|
120
172
|
'Response does not contain __error__': () => json?.__error__ === undefined,
|
|
121
173
|
'Run advanced the thread by one turn': () =>
|
|
122
174
|
json == null || plan == null || json.counter === plan.steps,
|
|
175
|
+
'Cold state read succeeded': () => result.data?.stateStatus == null,
|
|
123
176
|
'Delta storage is as expected': () => result.data?.guardFailure == null,
|
|
124
177
|
'Cold re-read matches the run': () => result.data?.drift == null,
|
|
125
178
|
});
|
|
@@ -131,6 +184,7 @@ export class DeltaTurns extends BenchmarkRunner {
|
|
|
131
184
|
const detail = [
|
|
132
185
|
plan ? `turn=${plan.steps} graph=${plan.graphId}` : '',
|
|
133
186
|
res ? `status=${res.status}` : '',
|
|
187
|
+
result.data?.stateStatus ? `state_status=${result.data.stateStatus}` : '',
|
|
134
188
|
result.data?.guardFailure ? `guard=${result.data.guardFailure}` : '',
|
|
135
189
|
result.data?.drift ? `drift=${result.data.drift}` : '',
|
|
136
190
|
]
|
|
@@ -139,7 +193,9 @@ export class DeltaTurns extends BenchmarkRunner {
|
|
|
139
193
|
logFailure(DeltaTurns.toString(), result, { extra: detail });
|
|
140
194
|
recordError(
|
|
141
195
|
classifyError({
|
|
142
|
-
response:
|
|
196
|
+
response: result.data?.stateStatus
|
|
197
|
+
? result.responses?.state ?? null
|
|
198
|
+
: res ?? result.responses?.create_thread ?? null,
|
|
143
199
|
hasApiError: json?.__error__ !== undefined,
|
|
144
200
|
missingMessage: result.data?.drift != null || result.data?.guardFailure != null,
|
|
145
201
|
}),
|
|
@@ -167,3 +223,22 @@ function safeJson(body: string | null | ArrayBuffer): Record<string, any> | null
|
|
|
167
223
|
return null;
|
|
168
224
|
}
|
|
169
225
|
}
|
|
226
|
+
|
|
227
|
+
function coldDigest(values: Record<string, unknown> | undefined): string | null {
|
|
228
|
+
if (values == null || typeof values !== 'object') return null;
|
|
229
|
+
// Reproduce the graph's payload_digest: every bytes channel in order, then
|
|
230
|
+
// the concatenated message contents. Absent channels default to empty, so
|
|
231
|
+
// they add nothing, exactly as the unwritten bytes channels do server-side.
|
|
232
|
+
const hasher = crypto.createHash('sha256');
|
|
233
|
+
for (const name of PAYLOAD_CHANNELS) {
|
|
234
|
+
const stored = values[name];
|
|
235
|
+
if (typeof stored === 'string' && stored.length > 0) {
|
|
236
|
+
hasher.update(encoding.b64decode(stored));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const messages = values.messages;
|
|
240
|
+
if (Array.isArray(messages) && messages.length > 0) {
|
|
241
|
+
hasher.update(messages.map((m) => String((m as { content?: unknown })?.content ?? '')).join(''));
|
|
242
|
+
}
|
|
243
|
+
return hasher.digest('hex');
|
|
244
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { BenchmarkRunner } from './benchmark-runner.js';
|
|
2
|
+
import type { ErrorMetrics } from './benchmark-runner.js';
|
|
3
|
+
import { check } from 'k6';
|
|
4
|
+
import http from 'k6/http';
|
|
5
|
+
import type { BenchmarkResult, BenchmarkGraphOptions, HttpResponse } from './types.js';
|
|
6
|
+
import { addResponse, okResult, failResult } from './types.js';
|
|
7
|
+
import { classifyError, recordError } from './classify-error.js';
|
|
8
|
+
import { logFailure } from './log-failure.js';
|
|
9
|
+
|
|
10
|
+
interface InterruptResumeData {
|
|
11
|
+
paused: ReturnType<typeof http.post>;
|
|
12
|
+
resumed: ReturnType<typeof http.post>;
|
|
13
|
+
expectedSteps: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pause a run mid-way and resume it. The virtual user is the human: it answers
|
|
18
|
+
* at once. What is measured is the server's work around the pause: writing the
|
|
19
|
+
* paused checkpoint, and on resume reading the whole thread state back before
|
|
20
|
+
* continuing, which is the most expensive read a delta channel can face.
|
|
21
|
+
*/
|
|
22
|
+
export class InterruptResume extends BenchmarkRunner {
|
|
23
|
+
static run(
|
|
24
|
+
baseUrl: string,
|
|
25
|
+
requestParams: Record<string, unknown>,
|
|
26
|
+
benchmarkGraphOptions: BenchmarkGraphOptions
|
|
27
|
+
): BenchmarkResult<InterruptResumeData> {
|
|
28
|
+
const base = benchmarkGraphOptions.context;
|
|
29
|
+
const context = {
|
|
30
|
+
...base,
|
|
31
|
+
interrupt_at: base.interrupt_at || Math.max(1, Math.ceil(base.steps / 2)),
|
|
32
|
+
};
|
|
33
|
+
const config = { recursion_limit: Math.max(context.expand, context.steps) + 4 };
|
|
34
|
+
const responses: Record<string, HttpResponse> = {};
|
|
35
|
+
|
|
36
|
+
const thread = http.post(`${baseUrl}/threads`, '{}', requestParams);
|
|
37
|
+
addResponse(responses, 'create_thread', thread);
|
|
38
|
+
if (thread.status !== 200) {
|
|
39
|
+
return failResult('create_thread', responses) as BenchmarkResult<InterruptResumeData>;
|
|
40
|
+
}
|
|
41
|
+
const threadId = (thread.json() as { thread_id: string }).thread_id;
|
|
42
|
+
const url = `${baseUrl}/threads/${threadId}/runs/wait`;
|
|
43
|
+
|
|
44
|
+
const paused = http.post(
|
|
45
|
+
url,
|
|
46
|
+
JSON.stringify({ assistant_id: benchmarkGraphOptions.graph_id, input: {}, context, config }),
|
|
47
|
+
requestParams
|
|
48
|
+
);
|
|
49
|
+
addResponse(responses, 'pause', paused);
|
|
50
|
+
if (paused.status !== 200) {
|
|
51
|
+
return failResult('pause', responses) as BenchmarkResult<InterruptResumeData>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const resumed = http.post(
|
|
55
|
+
url,
|
|
56
|
+
JSON.stringify({
|
|
57
|
+
assistant_id: benchmarkGraphOptions.graph_id,
|
|
58
|
+
command: { resume: true },
|
|
59
|
+
context,
|
|
60
|
+
config,
|
|
61
|
+
}),
|
|
62
|
+
requestParams
|
|
63
|
+
);
|
|
64
|
+
addResponse(responses, 'resume', resumed);
|
|
65
|
+
return okResult(responses, { paused, resumed, expectedSteps: context.steps });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static validate(
|
|
69
|
+
result: BenchmarkResult<InterruptResumeData>,
|
|
70
|
+
errorMetrics: ErrorMetrics
|
|
71
|
+
): boolean {
|
|
72
|
+
const data = result.data;
|
|
73
|
+
const pausedJson = safeJson(data?.paused?.body);
|
|
74
|
+
const resumedJson = safeJson(data?.resumed?.body);
|
|
75
|
+
let success = false;
|
|
76
|
+
try {
|
|
77
|
+
success = check(result, {
|
|
78
|
+
'Pause request succeeded': () => (data?.paused?.status ?? 0) === 200,
|
|
79
|
+
'Run paused on an interrupt': () => Array.isArray(pausedJson?.__interrupt__) && pausedJson.__interrupt__.length > 0,
|
|
80
|
+
'Resume request succeeded': () => (data?.resumed?.status ?? 0) === 200,
|
|
81
|
+
'Resumed run finished every step': () =>
|
|
82
|
+
resumedJson != null &&
|
|
83
|
+
resumedJson.__interrupt__ === undefined &&
|
|
84
|
+
resumedJson.__error__ === undefined &&
|
|
85
|
+
resumedJson.counter === data?.expectedSteps,
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.log(`Unknown error checking result: ${(error as Error).message}`);
|
|
89
|
+
}
|
|
90
|
+
if (!success) {
|
|
91
|
+
logFailure(InterruptResume.toString(), result, {
|
|
92
|
+
extra: `pause=${data?.paused?.status} resume=${data?.resumed?.status} counter=${resumedJson?.counter} expected=${data?.expectedSteps}`,
|
|
93
|
+
});
|
|
94
|
+
recordError(
|
|
95
|
+
classifyError({
|
|
96
|
+
response: data?.resumed ?? data?.paused ?? result.responses?.create_thread ?? null,
|
|
97
|
+
hasApiError: resumedJson?.__error__ !== undefined || pausedJson?.__error__ !== undefined,
|
|
98
|
+
missingMessage: resumedJson != null && resumedJson.counter !== data?.expectedSteps,
|
|
99
|
+
}),
|
|
100
|
+
errorMetrics
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return success;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static toString(): string {
|
|
107
|
+
return 'interrupt_resume';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function safeJson(body: unknown): Record<string, any> | null {
|
|
112
|
+
if (typeof body !== 'string') return null;
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(body);
|
|
115
|
+
return parsed != null && typeof parsed === 'object' ? parsed : null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|