agentseek-api 0.2.0__tar.gz → 0.2.1__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.
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/CHANGELOG.md +15 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/PKG-INFO +2 -2
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/README.md +1 -1
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/README.zh-CN.md +1 -1
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/pyproject.toml +1 -1
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test-redis-runtime.sh +25 -0
- agentseek_api-0.2.1/src/agentseek_api/__init__.py +1 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/run_executor.py +16 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/stream_persistence.py +20 -5
- agentseek_api-0.2.1/tests/integration/test_live_redis_stream_persistence.py +75 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_stream_persistence.py +85 -1
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_run_executor.py +136 -1
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/uv.lock +1 -1
- agentseek_api-0.2.0/src/agentseek_api/__init__.py +0 -1
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/.dockerignore +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/.env.example +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/.github/workflows/ci.yml +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/.github/workflows/live-provider-streaming.yml +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/.github/workflows/release.yml +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/.gitignore +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/AGENTS.md +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/CONTRIBUTING.md +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/LICENSE +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/Makefile +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/README.md +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/assistant_config/agentseek.json +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/assistant_config/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/auth/api_key_auth.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/auth/bearer_token_auth.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/auth/custom_backend.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/auth/jwt.md +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/auth/jwt_auth.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/custom_routes/app.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/docker_ci_auth/auth_backend.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/docker_ci_auth/manifest.json +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/external_graph/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/external_graph/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/external_graph/manifest.json +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/external_graph/run.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/react_agent/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/react_agent/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/store_memory/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/stress_test/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/stress_test/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/stress_tool_agent/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/stress_tool_agent/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/subgraph_agent/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/subgraph_agent/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/subgraph_hitl_agent/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/graphs/subgraph_hitl_agent/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/live_provider_graphs/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/live_provider_graphs/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/live_provider_graphs/manifest.json +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/minimal_agentseek/agentseek.json +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/minimal_agentseek/graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/run_sample_graphs.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/examples/sample_graphs_manifest.json +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/build_live_provider_matrix.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/seekdb_checkpoint_smoke.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/seekdb_embed_launcher.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test-checkpoints.sh +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test-cli-dev-samples.sh +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test-cli-docker.sh +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test-live-provider.sh +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test-seekdb.sh +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test_cli_config_autodiscovery.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test_cli_dev_studio_smoke.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test_cli_embed_serve_smoke.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/test_cli_serve_smoke.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/scripts/verify_docker_api.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/a2a_server.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/api/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/api/assistants.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/api/crons.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/api/runs.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/api/stateless_runs.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/api/store.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/api/streaming.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/api/threads.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/cli.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/a2a_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/app_loader.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/auth_deps.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/auth_middleware.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/config_file.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/content_type_fix.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/cors_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/database.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/http_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/mcp_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/oceanbase_checkpointer.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/orm.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/runtime_store.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/core/store_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/main.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/mcp_server.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/models/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/models/api.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/models/auth.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/models/protocol.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/scheduler.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/cron_models.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/cron_rrule.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/cron_scheduler.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/cron_service.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/cron_webhooks.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/default_assistants.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/executor.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/langgraph_service.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/redis_queue.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/run_jobs.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/run_preparation.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/run_state.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/sample_graphs.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/sse.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/stream_modes.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/thread_checkpoint_store.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/thread_protocol.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/thread_service.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/settings.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/worker.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/conftest.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/__init__.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/conftest.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/e2e_inprocess_flow.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/e2e_live_http_flow.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/e2e_live_http_multi_graph.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/e2e_live_http_resume_flow.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/fixtures/langgraph.store-e2e.json +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/live_provider_helpers.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/test_a2a_live.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/test_cron_api_live.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/test_cron_scheduler_live.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/test_langsmith_compat_live.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/test_live_provider_api.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/test_live_provider_mcp.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/test_mcp_live.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/e2e/test_real_api_flow.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_a2a_endpoint.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_agent_card.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_agents_aliases.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_assistants_compat.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_assistants_crud.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_assistants_ordering.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_auth_coverage_gaps.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_auth_handlers.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_auth_route_enforcement.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_auth_scope_matrix.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_core_api_flow.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_cron_api.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_langsmith_compat_coverage.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_langsmith_compat_extra.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_live_provider_streaming.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_mcp_endpoint.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_metadata_db_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_protocol_v2_streaming.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_run_cancellation_async.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_compat.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_crud.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_ordering.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_resume.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_streaming.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_streaming_errors.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_validation.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_wait_and_get.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_runs_wait_timeout.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_scheduler_runtime.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_stateless_runs_crud.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_store_api.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_system_endpoints.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_threads_compat.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_threads_crud.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/integration/test_threads_ordering.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_a2a_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_a2a_server.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_app_loader.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_assistant_routes_unit.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_auth.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_auth_deps.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_auth_internals.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_checkpoint_guards.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_cli.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_content_type_fix.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_cron_rrule.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_cron_service.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_cron_webhooks.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_custom_routers.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_database_manager.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_default_assistants.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_examples.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_executor.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_graph_manifest.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_http_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_langgraph_service.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_live_provider_e2e_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_live_provider_graphs.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_live_provider_matrix.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_mcp_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_mcp_server.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_oceanbase_checkpointer.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_openapi_auth_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_redis_queue.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_run_jobs.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_run_preparation.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_run_state.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_runtime_store.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_sample_graphs.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_scheduler.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_scheduler_process.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_seekdb_checkpoint_smoke.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_seekdb_embed_launcher.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_sse_keepalive.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_store_config.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_stream_modes.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_stream_persistence_helpers.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_thread_checkpoint_store.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_thread_protocol.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_thread_routes_unit.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_thread_service.py +0 -0
- {agentseek_api-0.2.0 → agentseek_api-0.2.1}/tests/unit/test_worker.py +0 -0
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to AgentSeek API are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.2.1 - 2026-07-14
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Preserved empty JSON arrays in Redis thread-stream events by avoiding a Lua
|
|
10
|
+
cjson decode/encode round trip, while keeping generated envelope fields
|
|
11
|
+
authoritative.
|
|
12
|
+
- Mirrored completed tool, human, and system messages to `messages-tuple` when
|
|
13
|
+
requested so LangGraph SDK clients receive tool results before final-answer
|
|
14
|
+
streaming completes.
|
|
15
|
+
|
|
16
|
+
### Upgrade notes
|
|
17
|
+
|
|
18
|
+
- No schema or configuration migration is required from 0.2.0.
|
|
19
|
+
|
|
5
20
|
## 0.2.0 - 2026-07-12
|
|
6
21
|
|
|
7
22
|
### Highlights
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentseek-api
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: AgentSeek API core runtime with OceanBase checkpoints.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -216,7 +216,7 @@ When the server starts it prints the banner and local URLs:
|
|
|
216
216
|
╠═╣│ ┬├┤ │││ │ ╚═╗├┤ ├┤ ├┴┐
|
|
217
217
|
╩ ╩└─┘└─┘┘└┘ ┴ ╚═╝└─┘└─┘┴ ┴
|
|
218
218
|
|
|
219
|
-
AgentSeek v0.2.
|
|
219
|
+
AgentSeek v0.2.1
|
|
220
220
|
|
|
221
221
|
- 🚀 API: http://localhost:2024
|
|
222
222
|
- 📚 Docs: http://localhost:2024/docs
|
|
@@ -9,6 +9,7 @@ CONFIG_PATH="${CONFIG_PATH:-examples/docker_ci_auth/manifest.json}"
|
|
|
9
9
|
NETWORK_NAME="${NETWORK_NAME:-agentseek-redis-runtime}"
|
|
10
10
|
BACKEND_CONTAINER="${BACKEND_CONTAINER:-agentseek-redis-backend}"
|
|
11
11
|
REDIS_CONTAINER="${REDIS_CONTAINER:-agentseek-redis}"
|
|
12
|
+
REDIS_HOST_PORT="${REDIS_HOST_PORT:-6391}"
|
|
12
13
|
API_CONTAINER="${API_CONTAINER:-agentseek-redis-api}"
|
|
13
14
|
WORKER_CONTAINER="${WORKER_CONTAINER:-agentseek-redis-worker}"
|
|
14
15
|
API_PORT="${API_PORT:-8126}"
|
|
@@ -113,6 +114,21 @@ PY
|
|
|
113
114
|
fi
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
wait_for_redis() {
|
|
118
|
+
local attempt
|
|
119
|
+
|
|
120
|
+
for ((attempt = 1; attempt <= 30; attempt++)); do
|
|
121
|
+
if docker exec "$REDIS_CONTAINER" redis-cli ping >/dev/null 2>&1; then
|
|
122
|
+
return 0
|
|
123
|
+
fi
|
|
124
|
+
sleep 1
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
echo "Redis did not become ready" >&2
|
|
128
|
+
print_logs
|
|
129
|
+
return 1
|
|
130
|
+
}
|
|
131
|
+
|
|
116
132
|
ensure_database_exists() {
|
|
117
133
|
uv run python - <<'PY'
|
|
118
134
|
import os
|
|
@@ -252,8 +268,17 @@ docker rm -f "$REDIS_CONTAINER" >/dev/null 2>&1 || true
|
|
|
252
268
|
docker run -d \
|
|
253
269
|
--name "$REDIS_CONTAINER" \
|
|
254
270
|
--network "$NETWORK_NAME" \
|
|
271
|
+
-p "127.0.0.1:${REDIS_HOST_PORT}:6379" \
|
|
255
272
|
redis:7-alpine >/dev/null
|
|
256
273
|
|
|
274
|
+
wait_for_redis
|
|
275
|
+
|
|
276
|
+
if ! AGENTSEEK_TEST_REDIS_URL="redis://127.0.0.1:${REDIS_HOST_PORT}/0" \
|
|
277
|
+
uv run pytest tests/integration/test_live_redis_stream_persistence.py -q; then
|
|
278
|
+
print_logs
|
|
279
|
+
exit 1
|
|
280
|
+
fi
|
|
281
|
+
|
|
257
282
|
wait_for_backend
|
|
258
283
|
ensure_database_exists
|
|
259
284
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -952,6 +952,22 @@ async def execute_run(
|
|
|
952
952
|
namespace=protocol_namespace,
|
|
953
953
|
run_id=run_id,
|
|
954
954
|
)
|
|
955
|
+
# LangGraph SDK 1.x useStream subscribes to
|
|
956
|
+
# messages-tuple, not messages/complete. Mirror the
|
|
957
|
+
# completed non-AI message onto that requested
|
|
958
|
+
# channel so ToolMessage resolves the pending tool
|
|
959
|
+
# call while the run is still streaming.
|
|
960
|
+
if _want_messages_tuple:
|
|
961
|
+
event_metadata = (
|
|
962
|
+
_normalize_stream_value(stream_event.get("metadata", {})) or {}
|
|
963
|
+
)
|
|
964
|
+
await apublish_messages_tuple(
|
|
965
|
+
thread_id,
|
|
966
|
+
chunk=msg_dump,
|
|
967
|
+
metadata=event_metadata,
|
|
968
|
+
namespace=protocol_namespace,
|
|
969
|
+
run_id=run_id,
|
|
970
|
+
)
|
|
955
971
|
continue
|
|
956
972
|
if _want_messages_tuple:
|
|
957
973
|
chunk_dump = _normalize_stream_value(message)
|
{agentseek_api-0.2.0 → agentseek_api-0.2.1}/src/agentseek_api/services/stream_persistence.py
RENAMED
|
@@ -17,6 +17,7 @@ _RUN_STREAM_SEQ_KEY_PREFIX = "agentseek:runs:stream-seq"
|
|
|
17
17
|
_THREAD_STREAM_SEQ_KEY_PREFIX = "agentseek:threads:stream-seq"
|
|
18
18
|
_RUN_STREAM_KEY_PREFIX = "agentseek:runs:stream"
|
|
19
19
|
_THREAD_STREAM_KEY_PREFIX = "agentseek:threads:stream"
|
|
20
|
+
_THREAD_STREAM_ENVELOPE_FIELDS = frozenset({"type", "event_id", "seq"})
|
|
20
21
|
_redis_client: Redis | None = None
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
@@ -24,11 +25,23 @@ _APPEND_REDIS_STREAM_EVENT_SCRIPT = """
|
|
|
24
25
|
local seq = redis.call('INCR', KEYS[1])
|
|
25
26
|
local payload = ARGV[1]
|
|
26
27
|
if ARGV[4] ~= '' then
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
-- Inject type/event_id/seq WITHOUT a cjson decode/encode round-trip.
|
|
29
|
+
-- Redis' bundled lua-cjson cannot distinguish an empty array from an empty
|
|
30
|
+
-- object, so cjson.encode(cjson.decode('{"tool_calls":[]}')) returns
|
|
31
|
+
-- '{"tool_calls":{}}'. That silently corrupts every streamed message
|
|
32
|
+
-- (tool_calls / invalid_tool_calls become {}), and langgraph-sdk's
|
|
33
|
+
-- convertToChunk() then throws on `{}.map`, so the client cannot concat
|
|
34
|
+
-- message chunks by id and each token replaces the previous one instead of
|
|
35
|
+
-- accumulating. Splice the header in as a string to keep the original
|
|
36
|
+
-- payload (and its empty arrays) byte-for-byte intact.
|
|
37
|
+
local rest = string.sub(payload, 2)
|
|
38
|
+
local event_id = cjson.encode(ARGV[4] .. ':' .. tostring(seq))
|
|
39
|
+
local head = '{"type":"event","event_id":' .. event_id .. ',"seq":' .. tostring(seq)
|
|
40
|
+
if rest == '}' then
|
|
41
|
+
payload = head .. '}'
|
|
42
|
+
else
|
|
43
|
+
payload = head .. ',' .. rest
|
|
44
|
+
end
|
|
32
45
|
end
|
|
33
46
|
redis.call('XADD', KEYS[2], 'MAXLEN', '~', ARGV[2], tostring(seq) .. '-0', 'payload', payload)
|
|
34
47
|
redis.call('EXPIRE', KEYS[2], ARGV[3])
|
|
@@ -70,6 +83,8 @@ async def _append_redis_stream_event_atomic(
|
|
|
70
83
|
payload: dict[str, Any],
|
|
71
84
|
event_prefix: str = "",
|
|
72
85
|
) -> tuple[int, dict[str, Any]]:
|
|
86
|
+
if event_prefix:
|
|
87
|
+
payload = {key: value for key, value in payload.items() if key not in _THREAD_STREAM_ENVELOPE_FIELDS}
|
|
73
88
|
result = await _get_redis_client().eval(
|
|
74
89
|
_APPEND_REDIS_STREAM_EVENT_SCRIPT,
|
|
75
90
|
2,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from redis.asyncio import from_url
|
|
7
|
+
|
|
8
|
+
from agentseek_api.services import stream_persistence as stream_module
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_TEST_REDIS_URL = os.getenv("AGENTSEEK_TEST_REDIS_URL")
|
|
12
|
+
pytestmark = pytest.mark.skipif(
|
|
13
|
+
not _TEST_REDIS_URL,
|
|
14
|
+
reason="AGENTSEEK_TEST_REDIS_URL is required for live Redis script tests",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize(
|
|
19
|
+
"payload",
|
|
20
|
+
[
|
|
21
|
+
pytest.param(
|
|
22
|
+
{
|
|
23
|
+
"method": "messages/partial",
|
|
24
|
+
"params": {
|
|
25
|
+
"data": {
|
|
26
|
+
"tool_calls": [],
|
|
27
|
+
"invalid_tool_calls": [],
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
id="message-with-empty-arrays",
|
|
32
|
+
),
|
|
33
|
+
pytest.param({}, id="empty-object"),
|
|
34
|
+
pytest.param(
|
|
35
|
+
{
|
|
36
|
+
"type": "payload-event",
|
|
37
|
+
"event_id": "payload-event-id",
|
|
38
|
+
"seq": 0,
|
|
39
|
+
"method": "values",
|
|
40
|
+
},
|
|
41
|
+
id="reserved-envelope-fields",
|
|
42
|
+
),
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
@pytest.mark.asyncio
|
|
46
|
+
async def test_thread_stream_lua_splices_header_without_reencoding_payload(
|
|
47
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
48
|
+
payload: dict[str, object],
|
|
49
|
+
) -> None:
|
|
50
|
+
assert _TEST_REDIS_URL is not None
|
|
51
|
+
redis = from_url(_TEST_REDIS_URL, decode_responses=True)
|
|
52
|
+
thread_id = 'thread-"' + "\\" + uuid4().hex[:8]
|
|
53
|
+
sequence_key = f"agentseek:threads:stream-seq:{thread_id}"
|
|
54
|
+
stream_key = f"agentseek:threads:stream:{thread_id}"
|
|
55
|
+
monkeypatch.setattr(stream_module, "_redis_client", redis)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
seq, event = await stream_module.append_redis_thread_stream_event(thread_id, payload)
|
|
59
|
+
rows = await redis.xrange(stream_key, min="-", max="+")
|
|
60
|
+
finally:
|
|
61
|
+
await redis.delete(sequence_key, stream_key)
|
|
62
|
+
await redis.aclose()
|
|
63
|
+
|
|
64
|
+
assert seq == 1
|
|
65
|
+
assert event["type"] == "event"
|
|
66
|
+
assert event["event_id"] == f"{thread_id}:1"
|
|
67
|
+
assert event["seq"] == 1
|
|
68
|
+
payload_body = {key: value for key, value in payload.items() if key not in {"type", "event_id", "seq"}}
|
|
69
|
+
expected_event = {"type": "event", "event_id": f"{thread_id}:1", "seq": 1, **payload_body}
|
|
70
|
+
assert event == expected_event
|
|
71
|
+
assert len(rows) == 1
|
|
72
|
+
stored_payload = rows[0][1]["payload"]
|
|
73
|
+
stored_event = json.loads(stored_payload)
|
|
74
|
+
assert stored_event == event
|
|
75
|
+
assert stored_payload == json.dumps(expected_event, ensure_ascii=False, separators=(",", ":"))
|
|
@@ -16,7 +16,12 @@ from agentseek_api.services import run_jobs as run_jobs_module
|
|
|
16
16
|
from agentseek_api.services import sse as sse_module
|
|
17
17
|
from agentseek_api.services import stream_persistence as stream_module
|
|
18
18
|
from agentseek_api.services.run_state import run_broker
|
|
19
|
-
from agentseek_api.services.thread_protocol import
|
|
19
|
+
from agentseek_api.services.thread_protocol import (
|
|
20
|
+
apublish_messages_complete,
|
|
21
|
+
apublish_messages_tuple,
|
|
22
|
+
publish_values_event,
|
|
23
|
+
thread_protocol_broker,
|
|
24
|
+
)
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
class FakeRedisCounter:
|
|
@@ -566,6 +571,85 @@ def test_create_run_stream_sends_keepalive_while_protocol_broker_is_idle(client:
|
|
|
566
571
|
assert events[1]["data"] == {"phase": "done"}
|
|
567
572
|
|
|
568
573
|
|
|
574
|
+
def test_redis_create_run_stream_replays_tool_message_tuple_for_sdk_subscribers(
|
|
575
|
+
client: TestClient,
|
|
576
|
+
monkeypatch,
|
|
577
|
+
) -> None:
|
|
578
|
+
fake_redis = FakeRedisCounter()
|
|
579
|
+
monkeypatch.setattr(stream_module.settings, "EXECUTOR_BACKEND", "redis")
|
|
580
|
+
monkeypatch.setattr(stream_module, "_redis_client", fake_redis)
|
|
581
|
+
monkeypatch.setattr(runs_api, "REDIS_STREAM_POLL_INTERVAL_SECONDS", 0)
|
|
582
|
+
|
|
583
|
+
thread_id, run_id = client.portal.call(lambda: _seed_run(status="success"))
|
|
584
|
+
created = RunRead(
|
|
585
|
+
run_id=run_id,
|
|
586
|
+
thread_id=thread_id,
|
|
587
|
+
assistant_id="assistant",
|
|
588
|
+
status="success",
|
|
589
|
+
output={},
|
|
590
|
+
interrupts=None,
|
|
591
|
+
last_error=None,
|
|
592
|
+
created_at=None,
|
|
593
|
+
updated_at=None,
|
|
594
|
+
metadata={},
|
|
595
|
+
kwargs={},
|
|
596
|
+
multitask_strategy="enqueue",
|
|
597
|
+
)
|
|
598
|
+
tool_message = {
|
|
599
|
+
"content": "42 characters",
|
|
600
|
+
"additional_kwargs": {},
|
|
601
|
+
"response_metadata": {},
|
|
602
|
+
"type": "tool",
|
|
603
|
+
"name": None,
|
|
604
|
+
"id": "tool-message-1",
|
|
605
|
+
"tool_call_id": "call-character-count",
|
|
606
|
+
"artifact": None,
|
|
607
|
+
"status": "success",
|
|
608
|
+
}
|
|
609
|
+
metadata = {
|
|
610
|
+
"langgraph_node": "tools",
|
|
611
|
+
"langgraph_checkpoint_ns": "tools:task-1",
|
|
612
|
+
"provider": "deterministic",
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
client.portal.call(
|
|
616
|
+
lambda: apublish_messages_complete(
|
|
617
|
+
thread_id,
|
|
618
|
+
messages=[tool_message],
|
|
619
|
+
namespace=["tools:task-1"],
|
|
620
|
+
run_id=run_id,
|
|
621
|
+
)
|
|
622
|
+
)
|
|
623
|
+
client.portal.call(
|
|
624
|
+
lambda: apublish_messages_tuple(
|
|
625
|
+
thread_id,
|
|
626
|
+
chunk=tool_message,
|
|
627
|
+
metadata=metadata,
|
|
628
|
+
namespace=["tools:task-1"],
|
|
629
|
+
run_id=run_id,
|
|
630
|
+
)
|
|
631
|
+
)
|
|
632
|
+
thread_protocol_broker.delete_thread(thread_id)
|
|
633
|
+
|
|
634
|
+
response = runs_api._build_create_run_stream_response(
|
|
635
|
+
thread_id=thread_id,
|
|
636
|
+
created=created,
|
|
637
|
+
user=User(identity="default_user", is_authenticated=True),
|
|
638
|
+
stream_modes=["messages-tuple", "values"],
|
|
639
|
+
after_seq=0,
|
|
640
|
+
location=(
|
|
641
|
+
f"/threads/{thread_id}/runs/{run_id}/stream"
|
|
642
|
+
"?stream_mode=messages-tuple&stream_mode=values"
|
|
643
|
+
),
|
|
644
|
+
content_location=f"/threads/{thread_id}/runs/{run_id}",
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
body = client.portal.call(_collect_stream_body, response)
|
|
648
|
+
events = _parse_sse(body)
|
|
649
|
+
assert [event["event"] for event in events] == ["metadata", "messages"]
|
|
650
|
+
assert events[1]["data"] == [tool_message, metadata]
|
|
651
|
+
|
|
652
|
+
|
|
569
653
|
def test_run_stream_persistence_uses_shared_seq_after_broker_reset(client: TestClient, monkeypatch) -> None:
|
|
570
654
|
fake_redis = FakeRedisCounter()
|
|
571
655
|
monkeypatch.setattr(stream_module.settings, "EXECUTOR_BACKEND", "redis")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
-
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage
|
|
4
|
+
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage, ToolMessage
|
|
5
5
|
from langgraph.constants import CONF, CONFIG_KEY_CHECKPOINTER
|
|
6
6
|
|
|
7
7
|
from agentseek_api.core.runtime_store import UserScopedStore
|
|
@@ -752,6 +752,141 @@ class FakeProtocolMultiMessageLangGraphService(FakeLangGraphService):
|
|
|
752
752
|
return FakeProtocolMultiMessageEntry()
|
|
753
753
|
|
|
754
754
|
|
|
755
|
+
class FakeProtocolToolMessageGraph(FakeGraph):
|
|
756
|
+
async def astream_events(self, prepared_input: dict, config: dict, version: str = "v2", **kwargs):
|
|
757
|
+
self.configs.append(config)
|
|
758
|
+
tool_message = ToolMessage(
|
|
759
|
+
content="42 characters",
|
|
760
|
+
tool_call_id="call-character-count",
|
|
761
|
+
id="tool-message-1",
|
|
762
|
+
)
|
|
763
|
+
tool_event = {
|
|
764
|
+
"event": "on_chain_stream",
|
|
765
|
+
"name": "tools",
|
|
766
|
+
"run_id": "tool-run",
|
|
767
|
+
"parent_ids": ["root-run"],
|
|
768
|
+
"metadata": {
|
|
769
|
+
"langgraph_node": "tools",
|
|
770
|
+
"langgraph_checkpoint_ns": "tools:task-1",
|
|
771
|
+
"provider": "deterministic",
|
|
772
|
+
},
|
|
773
|
+
"tags": ["graph:step:2"],
|
|
774
|
+
"data": {"chunk": {"messages": [tool_message]}},
|
|
775
|
+
}
|
|
776
|
+
yield tool_event
|
|
777
|
+
yield tool_event
|
|
778
|
+
yield {
|
|
779
|
+
"event": "on_chat_model_stream",
|
|
780
|
+
"name": "chat-model",
|
|
781
|
+
"run_id": "chat-run",
|
|
782
|
+
"parent_ids": ["root-run"],
|
|
783
|
+
"metadata": {"langgraph_node": "call_model"},
|
|
784
|
+
"tags": ["graph:step:3"],
|
|
785
|
+
"data": {"chunk": AIMessageChunk(content="Final answer", id="ai-message-1")},
|
|
786
|
+
}
|
|
787
|
+
yield {
|
|
788
|
+
"event": "on_chain_end",
|
|
789
|
+
"name": "fake-graph",
|
|
790
|
+
"run_id": "root-run",
|
|
791
|
+
"parent_ids": [],
|
|
792
|
+
"metadata": {},
|
|
793
|
+
"tags": [],
|
|
794
|
+
"data": {
|
|
795
|
+
"output": {
|
|
796
|
+
"output": {
|
|
797
|
+
"messages": [tool_message, AIMessage(content="Final answer", id="ai-message-1")]
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
class FakeProtocolToolMessageEntry(FakeEntry):
|
|
805
|
+
graph = FakeProtocolToolMessageGraph()
|
|
806
|
+
|
|
807
|
+
@staticmethod
|
|
808
|
+
def build_graph(_checkpointer=None) -> FakeProtocolToolMessageGraph:
|
|
809
|
+
return FakeProtocolToolMessageEntry.graph
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
class FakeProtocolToolMessageLangGraphService(FakeLangGraphService):
|
|
813
|
+
def get_entry(self, _graph_id: str | None) -> FakeProtocolToolMessageEntry:
|
|
814
|
+
return FakeProtocolToolMessageEntry()
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
async def _execute_protocol_tool_message_graph(
|
|
818
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
819
|
+
*,
|
|
820
|
+
stream_modes: list[str],
|
|
821
|
+
) -> list[dict[str, Any]]:
|
|
822
|
+
fake_db = FakeDBManager()
|
|
823
|
+
protocol_broker = ThreadProtocolEventBroker()
|
|
824
|
+
FakeProtocolToolMessageEntry.graph = FakeProtocolToolMessageGraph()
|
|
825
|
+
monkeypatch.setattr(
|
|
826
|
+
"agentseek_api.services.run_executor.get_langgraph_service",
|
|
827
|
+
lambda: FakeProtocolToolMessageLangGraphService(),
|
|
828
|
+
)
|
|
829
|
+
monkeypatch.setattr("agentseek_api.services.run_executor.db_manager", fake_db)
|
|
830
|
+
monkeypatch.setattr("agentseek_api.services.thread_protocol.thread_protocol_broker", protocol_broker)
|
|
831
|
+
|
|
832
|
+
await execute_run(
|
|
833
|
+
thread_id="t1",
|
|
834
|
+
run_id="r1",
|
|
835
|
+
payload={"hello": "world"},
|
|
836
|
+
user_id="user-1",
|
|
837
|
+
kwargs={"stream_modes": stream_modes},
|
|
838
|
+
)
|
|
839
|
+
return protocol_broker._events["t1"]
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
@pytest.mark.asyncio
|
|
843
|
+
async def test_execute_run_mirrors_tool_message_to_requested_tuple_stream_once_and_before_final_answer(
|
|
844
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
845
|
+
) -> None:
|
|
846
|
+
thread_events = await _execute_protocol_tool_message_graph(
|
|
847
|
+
monkeypatch,
|
|
848
|
+
stream_modes=["messages-tuple", "values"],
|
|
849
|
+
)
|
|
850
|
+
complete_events = [event for event in thread_events if event["method"] == "messages/complete"]
|
|
851
|
+
tuple_events = [event for event in thread_events if event["method"] == "messages-tuple"]
|
|
852
|
+
tool_tuples = [event for event in tuple_events if event["params"]["data"][0]["type"] == "tool"]
|
|
853
|
+
|
|
854
|
+
assert len(complete_events) == 1
|
|
855
|
+
assert len(tool_tuples) == 1
|
|
856
|
+
|
|
857
|
+
tool_tuple = tool_tuples[0]
|
|
858
|
+
tool_payload, metadata = tool_tuple["params"]["data"]
|
|
859
|
+
assert tool_payload["content"] == "42 characters"
|
|
860
|
+
assert tool_payload["id"] == "tool-message-1"
|
|
861
|
+
assert tool_payload["tool_call_id"] == "call-character-count"
|
|
862
|
+
assert metadata == {
|
|
863
|
+
"langgraph_node": "tools",
|
|
864
|
+
"langgraph_checkpoint_ns": "tools:task-1",
|
|
865
|
+
"provider": "deterministic",
|
|
866
|
+
}
|
|
867
|
+
assert tool_tuple["params"]["namespace"] == ["tools:task-1"]
|
|
868
|
+
assert tool_tuple["params"]["run_id"] == "r1"
|
|
869
|
+
assert complete_events[0]["params"]["data"] == [tool_payload]
|
|
870
|
+
|
|
871
|
+
final_answer_tuple = next(
|
|
872
|
+
event for event in tuple_events if event["params"]["data"][0].get("id") == "ai-message-1"
|
|
873
|
+
)
|
|
874
|
+
assert thread_events.index(tool_tuple) < thread_events.index(final_answer_tuple)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
@pytest.mark.asyncio
|
|
878
|
+
async def test_execute_run_keeps_tool_message_complete_without_unrequested_tuple_mirror(
|
|
879
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
880
|
+
) -> None:
|
|
881
|
+
thread_events = await _execute_protocol_tool_message_graph(monkeypatch, stream_modes=["values"])
|
|
882
|
+
complete_events = [event for event in thread_events if event["method"] == "messages/complete"]
|
|
883
|
+
tuple_events = [event for event in thread_events if event["method"] == "messages-tuple"]
|
|
884
|
+
|
|
885
|
+
assert len(complete_events) == 1
|
|
886
|
+
assert complete_events[0]["params"]["data"][0]["id"] == "tool-message-1"
|
|
887
|
+
assert tuple_events == []
|
|
888
|
+
|
|
889
|
+
|
|
755
890
|
@pytest.mark.asyncio
|
|
756
891
|
async def test_execute_run_publishes_incremental_protocol_messages_and_values(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
757
892
|
fake_db = FakeDBManager()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|