agent-starter-pack 0.3.3__py3-none-any.whl → 0.21.0__py3-none-any.whl
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.
- agent_starter_pack/agents/README.md +7 -0
- agents/langgraph_base_react/template/.templateconfig.yaml → agent_starter_pack/agents/adk_a2a_base/.template/templateconfig.yaml +5 -10
- agent_starter_pack/agents/adk_a2a_base/README.md +37 -0
- src/deployment_targets/cloud_run/Dockerfile → agent_starter_pack/agents/adk_a2a_base/app/__init__.py +2 -14
- agent_starter_pack/agents/adk_a2a_base/app/agent.py +70 -0
- agent_starter_pack/agents/adk_a2a_base/notebooks/adk_a2a_app_testing.ipynb +583 -0
- agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb → agent_starter_pack/agents/adk_a2a_base/notebooks/evaluating_adk_agent.ipynb +163 -199
- {agents/adk_base → agent_starter_pack/agents/adk_a2a_base}/tests/integration/test_agent.py +2 -2
- agents/adk_base/template/.templateconfig.yaml → agent_starter_pack/agents/adk_base/.template/templateconfig.yaml +4 -1
- {agents → agent_starter_pack/agents}/adk_base/README.md +1 -1
- agent_starter_pack/agents/adk_base/app/__init__.py +17 -0
- {agents → agent_starter_pack/agents}/adk_base/app/agent.py +5 -2
- {agents → agent_starter_pack/agents}/adk_base/notebooks/adk_app_testing.ipynb +128 -82
- agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb → agent_starter_pack/agents/adk_base/notebooks/evaluating_adk_agent.ipynb +181 -207
- agent_starter_pack/agents/adk_base/tests/integration/test_agent.py +58 -0
- agents/crewai_coding_crew/template/.templateconfig.yaml → agent_starter_pack/agents/adk_live/.template/templateconfig.yaml +5 -9
- agent_starter_pack/agents/adk_live/README.md +32 -0
- agent_starter_pack/agents/adk_live/app/__init__.py +17 -0
- agent_starter_pack/agents/adk_live/app/agent.py +51 -0
- agent_starter_pack/agents/adk_live/tests/unit/test_dummy.py +38 -0
- agents/agentic_rag/template/.templateconfig.yaml → agent_starter_pack/agents/agentic_rag/.template/templateconfig.yaml +7 -3
- {agents → agent_starter_pack/agents}/agentic_rag/README.md +1 -1
- agent_starter_pack/agents/agentic_rag/app/__init__.py +17 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/agent.py +8 -4
- {agents → agent_starter_pack/agents}/agentic_rag/notebooks/adk_app_testing.ipynb +128 -82
- agent_starter_pack/agents/agentic_rag/notebooks/evaluating_adk_agent.ipynb +1535 -0
- {agents → agent_starter_pack/agents}/agentic_rag/tests/integration/test_agent.py +3 -3
- agent_starter_pack/agents/langgraph_base/.template/templateconfig.yaml +31 -0
- agent_starter_pack/agents/langgraph_base/README.md +30 -0
- agent_starter_pack/agents/langgraph_base/app/__init__.py +17 -0
- agent_starter_pack/agents/langgraph_base/app/agent.py +34 -0
- {agents/crewai_coding_crew → agent_starter_pack/agents/langgraph_base}/notebooks/evaluating_langgraph_agent.ipynb +30 -17
- {agents/langgraph_base_react → agent_starter_pack/agents/langgraph_base}/tests/integration/test_agent.py +2 -2
- {src → agent_starter_pack}/base_template/.gitignore +5 -3
- agent_starter_pack/base_template/GEMINI.md +5 -0
- agent_starter_pack/base_template/Makefile +339 -0
- agent_starter_pack/base_template/README.md +267 -0
- agent_starter_pack/base_template/deployment/README.md +11 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/apis.tf +2 -2
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/apis.tf +6 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/iam.tf +12 -11
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/providers.tf +5 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/storage.tf +1 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/variables.tf +10 -10
- agent_starter_pack/base_template/deployment/terraform/dev/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +193 -0
- agent_starter_pack/base_template/deployment/terraform/github.tf +337 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/iam.tf +20 -41
- {src → agent_starter_pack}/base_template/deployment/terraform/locals.tf +10 -3
- {src/resources/setup_cicd → agent_starter_pack/base_template/deployment/terraform}/providers.tf +8 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/service_accounts.tf +7 -8
- agent_starter_pack/base_template/deployment/terraform/sql/completions.sql +138 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/storage.tf +7 -16
- {src → agent_starter_pack}/base_template/deployment/terraform/variables.tf +61 -28
- {src → agent_starter_pack}/base_template/deployment/terraform/vars/env.tfvars +6 -1
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +43 -0
- src/base_template/deployment/terraform/build_triggers.tf → agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +55 -38
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +206 -0
- {src → agent_starter_pack}/base_template/pyproject.toml +24 -37
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +132 -0
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +65 -0
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +259 -0
- src/base_template/deployment/cd/deploy-to-prod.yaml → agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +38 -30
- src/base_template/deployment/ci/pr_checks.yaml → agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/pr_checks.yaml +5 -5
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +322 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/telemetry.py +96 -0
- {src/base_template/app/utils → agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils}/typing.py +7 -9
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/__init__.py +25 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/part_converter.py +138 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/__init__.py +13 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/a2a_agent_executor.py +265 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/task_result_aggregator.py +152 -0
- agent_starter_pack/cli/commands/create.py +1256 -0
- agent_starter_pack/cli/commands/enhance.py +611 -0
- agent_starter_pack/cli/commands/list.py +203 -0
- agent_starter_pack/cli/commands/register_gemini_enterprise.py +1070 -0
- agent_starter_pack/cli/commands/setup_cicd.py +862 -0
- {src → agent_starter_pack}/cli/main.py +10 -2
- {src → agent_starter_pack}/cli/utils/cicd.py +139 -48
- agent_starter_pack/cli/utils/gcp.py +263 -0
- agent_starter_pack/cli/utils/logging.py +103 -0
- agent_starter_pack/cli/utils/remote_template.py +677 -0
- agent_starter_pack/cli/utils/template.py +1466 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/process_data.py +1 -1
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +20 -6
- {src → agent_starter_pack}/data_ingestion/pyproject.toml +1 -0
- {src → agent_starter_pack}/data_ingestion/uv.lock +602 -494
- agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +484 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +84 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +424 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/{% if cookiecutter.is_a2a %}helpers.py{% else %}unused_helpers.py{% endif %} +138 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +263 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/deploy.py +414 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +519 -0
- agent_starter_pack/deployment_targets/cloud_run/Dockerfile +51 -0
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +243 -0
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/service.tf +417 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +705 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +321 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/README.md +165 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +329 -0
- agent_starter_pack/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/fast_api_app.py +556 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package-lock.json +79 -1044
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package.json +1 -9
- agent_starter_pack/frontends/adk_live_react/frontend/src/App.tsx +65 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/Logger.tsx +8 -3
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/logger.scss +26 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/side-panel/SidePanel.tsx +516 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/side-panel/side-panel.scss +563 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/TranscriptionPreview.tsx +106 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/transcription-preview.scss +150 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-live-api.ts +8 -2
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/multimodal-live-types.ts +40 -2
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-recorder.ts +1 -1
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-streamer.ts +1 -1
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/multimodal-live-client.ts +210 -24
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/utils.ts +27 -5
- agent_starter_pack/resources/docs/adk-cheatsheet.md +1628 -0
- agent_starter_pack/resources/locks/uv-adk_a2a_base-agent_engine.lock +4966 -0
- agent_starter_pack/resources/locks/uv-adk_a2a_base-cloud_run.lock +5011 -0
- agent_starter_pack/resources/locks/uv-adk_base-agent_engine.lock +4946 -0
- agent_starter_pack/resources/locks/uv-adk_base-cloud_run.lock +4991 -0
- agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +4963 -0
- agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +5006 -0
- agent_starter_pack/resources/locks/uv-agentic_rag-agent_engine.lock +5487 -0
- agent_starter_pack/resources/locks/uv-agentic_rag-cloud_run.lock +5532 -0
- agent_starter_pack/resources/locks/uv-langgraph_base-agent_engine.lock +5788 -0
- agent_starter_pack/resources/locks/uv-langgraph_base-cloud_run.lock +5811 -0
- {src → agent_starter_pack}/utils/generate_locks.py +15 -12
- {src → agent_starter_pack}/utils/lock_utils.py +4 -7
- {src → agent_starter_pack}/utils/watch_and_rebuild.py +2 -2
- agent_starter_pack-0.21.0.dist-info/METADATA +182 -0
- agent_starter_pack-0.21.0.dist-info/RECORD +171 -0
- agent_starter_pack-0.21.0.dist-info/entry_points.txt +2 -0
- llm.txt +362 -0
- agent_starter_pack-0.3.3.dist-info/METADATA +0 -164
- agent_starter_pack-0.3.3.dist-info/RECORD +0 -176
- agent_starter_pack-0.3.3.dist-info/entry_points.txt +0 -2
- agents/crewai_coding_crew/README.md +0 -34
- agents/crewai_coding_crew/app/agent.py +0 -86
- agents/crewai_coding_crew/app/crew/config/agents.yaml +0 -39
- agents/crewai_coding_crew/app/crew/config/tasks.yaml +0 -37
- agents/crewai_coding_crew/app/crew/crew.py +0 -71
- agents/crewai_coding_crew/tests/integration/test_agent.py +0 -47
- agents/langgraph_base_react/README.md +0 -9
- agents/langgraph_base_react/app/agent.py +0 -73
- agents/live_api/README.md +0 -37
- agents/live_api/app/agent.py +0 -78
- agents/live_api/app/server.py +0 -196
- agents/live_api/app/templates.py +0 -51
- agents/live_api/app/vector_store.py +0 -55
- agents/live_api/template/.templateconfig.yaml +0 -29
- agents/live_api/tests/integration/test_server_e2e.py +0 -254
- agents/live_api/tests/load_test/load_test.py +0 -40
- agents/live_api/tests/unit/test_server.py +0 -143
- src/base_template/Makefile +0 -72
- src/base_template/README.md +0 -208
- src/base_template/app/__init__.py +0 -3
- src/base_template/app/utils/tracing.py +0 -155
- src/base_template/deployment/README.md +0 -126
- src/base_template/deployment/cd/staging.yaml +0 -216
- src/base_template/deployment/terraform/dev/log_sinks.tf +0 -63
- src/base_template/deployment/terraform/log_sinks.tf +0 -70
- src/base_template/deployment/terraform/providers.tf +0 -37
- src/cli/commands/create.py +0 -664
- src/cli/commands/setup_cicd.py +0 -829
- src/cli/utils/gcp.py +0 -117
- src/cli/utils/logging.py +0 -51
- src/cli/utils/template.py +0 -737
- src/deployment_targets/agent_engine/app/agent_engine_app.py +0 -336
- src/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -1025
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +0 -183
- src/deployment_targets/agent_engine/tests/load_test/README.md +0 -42
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +0 -107
- src/deployment_targets/cloud_run/app/server.py +0 -154
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +0 -249
- src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
- src/deployment_targets/cloud_run/tests/load_test/README.md +0 -83
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +0 -118
- src/deployment_targets/cloud_run/uv.lock +0 -6952
- src/frontends/live_api_react/frontend/src/App.tsx +0 -205
- src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +0 -217
- src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +0 -201
- src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +0 -161
- src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +0 -285
- src/frontends/streamlit/frontend/side_bar.py +0 -214
- src/frontends/streamlit/frontend/streamlit_app.py +0 -265
- src/frontends/streamlit/frontend/style/app_markdown.py +0 -37
- src/frontends/streamlit/frontend/utils/chat_utils.py +0 -67
- src/frontends/streamlit/frontend/utils/local_chat_history.py +0 -125
- src/frontends/streamlit/frontend/utils/message_editing.py +0 -59
- src/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -217
- src/frontends/streamlit/frontend/utils/stream_handler.py +0 -301
- src/frontends/streamlit/frontend/utils/title_summary.py +0 -94
- src/frontends/streamlit_adk/frontend/side_bar.py +0 -214
- src/frontends/streamlit_adk/frontend/streamlit_app.py +0 -314
- src/frontends/streamlit_adk/frontend/style/app_markdown.py +0 -37
- src/frontends/streamlit_adk/frontend/utils/chat_utils.py +0 -84
- src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +0 -110
- src/frontends/streamlit_adk/frontend/utils/message_editing.py +0 -61
- src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +0 -223
- src/frontends/streamlit_adk/frontend/utils/stream_handler.py +0 -311
- src/frontends/streamlit_adk/frontend/utils/title_summary.py +0 -129
- src/resources/containers/data_processing/Dockerfile +0 -27
- src/resources/containers/e2e-tests/Dockerfile +0 -19
- src/resources/idx/.idx/dev.nix +0 -57
- src/resources/idx/idx-template.json +0 -21
- src/resources/idx/idx-template.nix +0 -26
- src/resources/locks/uv-adk_base-agent_engine.lock +0 -5338
- src/resources/locks/uv-adk_base-cloud_run.lock +0 -5930
- src/resources/locks/uv-agentic_rag-agent_engine.lock +0 -5528
- src/resources/locks/uv-agentic_rag-cloud_run.lock +0 -6120
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +0 -6231
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +0 -6839
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +0 -5233
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +0 -5862
- src/resources/locks/uv-live_api-cloud_run.lock +0 -5832
- src/resources/setup_cicd/cicd_variables.tf +0 -41
- src/resources/setup_cicd/github.tf +0 -87
- {agents → agent_starter_pack/agents}/agentic_rag/app/retrievers.py +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/templates.py +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/vars/env.tfvars +0 -0
- {src → agent_starter_pack}/base_template/tests/unit/test_dummy.py +0 -0
- {src/deployment_targets/agent_engine/app/utils → agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils}/gcs.py +0 -0
- {src → agent_starter_pack}/cli/utils/__init__.py +0 -0
- {src → agent_starter_pack}/cli/utils/datastores.py +0 -0
- {src → agent_starter_pack}/cli/utils/version.py +0 -0
- {src → agent_starter_pack}/data_ingestion/README.md +0 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +0 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/pipeline.py +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/deployment_metadata.json +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/favicon.ico +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/index.html +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/robots.txt +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.scss +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.test.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/AudioPulse.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/audio-pulse.scss +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/mock-logs.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/contexts/LiveAPIContext.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-media-stream-mux.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-screen-capture.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-webcam.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.css +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/react-app-env.d.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/reportWebVitals.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/setupTests.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audioworklet-registry.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/store-logger.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/audio-processing.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/vol-meter.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/tsconfig.json +0 -0
- {agent_starter_pack-0.3.3.dist-info → agent_starter_pack-0.21.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.3.3.dist-info → agent_starter_pack-0.21.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
{%- if cookiecutter.agent_name == "adk_live" %}
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
from collections.abc import Iterator
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
import pytest
|
|
27
|
+
import requests
|
|
28
|
+
from websockets.asyncio.client import connect
|
|
29
|
+
|
|
30
|
+
# Configure logging
|
|
31
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
WS_URL = "ws://127.0.0.1:8000/ws"
|
|
35
|
+
FEEDBACK_URL = "http://127.0.0.1:8000/feedback"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def log_output(pipe: Any, log_func: Any) -> None:
|
|
39
|
+
"""Log the output from the given pipe."""
|
|
40
|
+
for line in iter(pipe.readline, ""):
|
|
41
|
+
log_func(line.strip())
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def start_server() -> subprocess.Popen[str]:
|
|
45
|
+
"""Start the server in local mode."""
|
|
46
|
+
command = [
|
|
47
|
+
sys.executable,
|
|
48
|
+
"-m",
|
|
49
|
+
"uvicorn",
|
|
50
|
+
"{{cookiecutter.agent_directory}}.fast_api_app:app",
|
|
51
|
+
"--host",
|
|
52
|
+
"0.0.0.0",
|
|
53
|
+
"--port",
|
|
54
|
+
"8000",
|
|
55
|
+
]
|
|
56
|
+
process = subprocess.Popen(
|
|
57
|
+
command,
|
|
58
|
+
stdout=subprocess.PIPE,
|
|
59
|
+
stderr=subprocess.PIPE,
|
|
60
|
+
text=True,
|
|
61
|
+
bufsize=1,
|
|
62
|
+
encoding="utf-8",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Start threads to log stdout and stderr in real-time
|
|
66
|
+
threading.Thread(
|
|
67
|
+
target=log_output, args=(process.stdout, logger.info), daemon=True
|
|
68
|
+
).start()
|
|
69
|
+
threading.Thread(
|
|
70
|
+
target=log_output, args=(process.stderr, logger.error), daemon=True
|
|
71
|
+
).start()
|
|
72
|
+
|
|
73
|
+
return process
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def wait_for_server(timeout: int = 60, interval: int = 1) -> bool:
|
|
77
|
+
"""Wait for the server to be ready."""
|
|
78
|
+
start_time = time.time()
|
|
79
|
+
while time.time() - start_time < timeout:
|
|
80
|
+
try:
|
|
81
|
+
response = requests.get("http://127.0.0.1:8000/docs", timeout=10)
|
|
82
|
+
if response.status_code == 200:
|
|
83
|
+
logger.info("Server is ready")
|
|
84
|
+
return True
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
time.sleep(interval)
|
|
88
|
+
logger.error(f"Server did not become ready within {timeout} seconds")
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.fixture(scope="module")
|
|
93
|
+
def server_fixture(request: Any) -> Iterator[subprocess.Popen[str]]:
|
|
94
|
+
"""Pytest fixture to start and stop the server for testing."""
|
|
95
|
+
logger.info("Starting server process")
|
|
96
|
+
server_process = start_server()
|
|
97
|
+
if not wait_for_server():
|
|
98
|
+
pytest.fail("Server failed to start")
|
|
99
|
+
logger.info("Server process started")
|
|
100
|
+
|
|
101
|
+
def stop_server() -> None:
|
|
102
|
+
logger.info("Stopping server process")
|
|
103
|
+
server_process.terminate()
|
|
104
|
+
try:
|
|
105
|
+
server_process.wait(timeout=5)
|
|
106
|
+
except subprocess.TimeoutExpired:
|
|
107
|
+
logger.warning("Server process did not terminate, killing it")
|
|
108
|
+
server_process.kill()
|
|
109
|
+
server_process.wait()
|
|
110
|
+
logger.info("Server process stopped")
|
|
111
|
+
|
|
112
|
+
request.addfinalizer(stop_server)
|
|
113
|
+
yield server_process
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.mark.asyncio
|
|
117
|
+
async def test_websocket_audio_input(server_fixture: subprocess.Popen[str]) -> None:
|
|
118
|
+
"""Test websocket with audio input in local mode."""
|
|
119
|
+
|
|
120
|
+
async def send_message(websocket: Any, message: dict[str, Any]) -> None:
|
|
121
|
+
"""Helper to send JSON messages."""
|
|
122
|
+
await websocket.send(json.dumps(message))
|
|
123
|
+
|
|
124
|
+
async def receive_message(websocket: Any, timeout: float = 5.0) -> dict[str, Any]:
|
|
125
|
+
"""Helper to receive messages with timeout."""
|
|
126
|
+
try:
|
|
127
|
+
response = await asyncio.wait_for(websocket.recv(), timeout=timeout)
|
|
128
|
+
if isinstance(response, bytes):
|
|
129
|
+
return json.loads(response.decode())
|
|
130
|
+
if isinstance(response, str):
|
|
131
|
+
return json.loads(response)
|
|
132
|
+
return response
|
|
133
|
+
except asyncio.TimeoutError as exc:
|
|
134
|
+
raise TimeoutError(
|
|
135
|
+
f"No response received within {timeout} seconds"
|
|
136
|
+
) from exc
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
await asyncio.sleep(2)
|
|
140
|
+
|
|
141
|
+
async with connect(WS_URL, ping_timeout=10, close_timeout=10) as websocket:
|
|
142
|
+
try:
|
|
143
|
+
# Wait for setupComplete
|
|
144
|
+
setup_response = await receive_message(websocket, timeout=10.0)
|
|
145
|
+
assert "setupComplete" in setup_response
|
|
146
|
+
logger.info("Received setupComplete")
|
|
147
|
+
|
|
148
|
+
# Send dummy audio chunk with user_id
|
|
149
|
+
dummy_audio = bytes([0] * 1024)
|
|
150
|
+
audio_msg = {
|
|
151
|
+
"user_id": "test-user",
|
|
152
|
+
"realtimeInput": {
|
|
153
|
+
"mediaChunks": [
|
|
154
|
+
{
|
|
155
|
+
"mimeType": "audio/pcm;rate=16000",
|
|
156
|
+
"data": dummy_audio.hex(),
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
await send_message(websocket, audio_msg)
|
|
162
|
+
logger.info("Sent audio chunk")
|
|
163
|
+
|
|
164
|
+
# Send text message to complete the turn (matching frontend format)
|
|
165
|
+
text_msg = {
|
|
166
|
+
"content": {
|
|
167
|
+
"role": "user",
|
|
168
|
+
"parts": [{"text": "Test audio"}],
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
await send_message(websocket, text_msg)
|
|
172
|
+
logger.info("Sent text completion")
|
|
173
|
+
|
|
174
|
+
# Collect responses
|
|
175
|
+
responses = []
|
|
176
|
+
for _ in range(10):
|
|
177
|
+
try:
|
|
178
|
+
response = await receive_message(websocket, timeout=5.0)
|
|
179
|
+
responses.append(response)
|
|
180
|
+
logger.info(f"Received: {response}")
|
|
181
|
+
|
|
182
|
+
if isinstance(response, dict) and response.get("turn_complete"):
|
|
183
|
+
break
|
|
184
|
+
except TimeoutError:
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
# Verify we got responses
|
|
188
|
+
assert len(responses) > 0, "No responses received"
|
|
189
|
+
|
|
190
|
+
logger.info(f"Audio test passed. Received {len(responses)} responses")
|
|
191
|
+
|
|
192
|
+
finally:
|
|
193
|
+
await websocket.close()
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(f"Audio test failed: {e}")
|
|
197
|
+
raise
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_feedback_endpoint(server_fixture: subprocess.Popen[str]) -> None:
|
|
201
|
+
"""Test the feedback endpoint."""
|
|
202
|
+
feedback_data = {
|
|
203
|
+
"score": 5,
|
|
204
|
+
"text": "Great response!",
|
|
205
|
+
"user_id": "test-user-123",
|
|
206
|
+
"session_id": "test-session-123",
|
|
207
|
+
"log_type": "feedback",
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
response = requests.post(FEEDBACK_URL, json=feedback_data, timeout=10)
|
|
211
|
+
assert response.status_code == 200
|
|
212
|
+
assert response.json() == {"status": "success"}
|
|
213
|
+
logger.info("Feedback endpoint test passed")
|
|
214
|
+
{%- else %}
|
|
215
|
+
|
|
216
|
+
import json
|
|
217
|
+
import logging
|
|
218
|
+
import os
|
|
219
|
+
import subprocess
|
|
220
|
+
import sys
|
|
221
|
+
import threading
|
|
222
|
+
import time
|
|
223
|
+
{%- if cookiecutter.is_a2a %}
|
|
224
|
+
import uuid
|
|
225
|
+
{%- endif %}
|
|
226
|
+
from collections.abc import Iterator
|
|
227
|
+
from typing import Any
|
|
228
|
+
|
|
229
|
+
import pytest
|
|
230
|
+
import requests
|
|
231
|
+
{%- if cookiecutter.is_a2a %}
|
|
232
|
+
from a2a.types import (
|
|
233
|
+
JSONRPCErrorResponse,
|
|
234
|
+
Message,
|
|
235
|
+
MessageSendParams,
|
|
236
|
+
Part,
|
|
237
|
+
Role,
|
|
238
|
+
SendMessageRequest,
|
|
239
|
+
SendMessageResponse,
|
|
240
|
+
SendStreamingMessageRequest,
|
|
241
|
+
SendStreamingMessageResponse,
|
|
242
|
+
TextPart,
|
|
243
|
+
)
|
|
244
|
+
{%- endif %}
|
|
245
|
+
from requests.exceptions import RequestException
|
|
246
|
+
|
|
247
|
+
# Configure logging
|
|
248
|
+
logging.basicConfig(level=logging.INFO)
|
|
249
|
+
logger = logging.getLogger(__name__)
|
|
250
|
+
|
|
251
|
+
BASE_URL = "http://127.0.0.1:8000/"
|
|
252
|
+
{%- if cookiecutter.is_a2a %}
|
|
253
|
+
A2A_RPC_URL = BASE_URL + "a2a/{{cookiecutter.agent_directory}}/"
|
|
254
|
+
AGENT_CARD_URL = A2A_RPC_URL + ".well-known/agent-card.json"
|
|
255
|
+
{%- elif cookiecutter.is_adk %}
|
|
256
|
+
STREAM_URL = BASE_URL + "run_sse"
|
|
257
|
+
{%- else %}
|
|
258
|
+
STREAM_URL = BASE_URL + "stream_messages"
|
|
259
|
+
{%- endif %}
|
|
260
|
+
FEEDBACK_URL = BASE_URL + "feedback"
|
|
261
|
+
|
|
262
|
+
HEADERS = {"Content-Type": "application/json"}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def log_output(pipe: Any, log_func: Any) -> None:
|
|
266
|
+
"""Log the output from the given pipe."""
|
|
267
|
+
for line in iter(pipe.readline, ""):
|
|
268
|
+
log_func(line.strip())
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def start_server() -> subprocess.Popen[str]:
|
|
272
|
+
"""Start the FastAPI server using subprocess and log its output."""
|
|
273
|
+
command = [
|
|
274
|
+
sys.executable,
|
|
275
|
+
"-m",
|
|
276
|
+
"uvicorn",
|
|
277
|
+
"{{cookiecutter.agent_directory}}.fast_api_app:app",
|
|
278
|
+
"--host",
|
|
279
|
+
"0.0.0.0",
|
|
280
|
+
"--port",
|
|
281
|
+
"8000",
|
|
282
|
+
]
|
|
283
|
+
env = os.environ.copy()
|
|
284
|
+
env["INTEGRATION_TEST"] = "TRUE"
|
|
285
|
+
{%- if cookiecutter.session_type == "agent_engine" %}
|
|
286
|
+
# Use in-memory session for local E2E tests instead of creating Agent Engine
|
|
287
|
+
env["USE_IN_MEMORY_SESSION"] = "true"
|
|
288
|
+
{%- endif %}
|
|
289
|
+
process = subprocess.Popen(
|
|
290
|
+
command,
|
|
291
|
+
stdout=subprocess.PIPE,
|
|
292
|
+
stderr=subprocess.PIPE,
|
|
293
|
+
text=True,
|
|
294
|
+
bufsize=1,
|
|
295
|
+
env=env,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Start threads to log stdout and stderr in real-time
|
|
299
|
+
threading.Thread(
|
|
300
|
+
target=log_output, args=(process.stdout, logger.info), daemon=True
|
|
301
|
+
).start()
|
|
302
|
+
threading.Thread(
|
|
303
|
+
target=log_output, args=(process.stderr, logger.error), daemon=True
|
|
304
|
+
).start()
|
|
305
|
+
|
|
306
|
+
return process
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def wait_for_server(timeout: int = 90, interval: int = 1) -> bool:
|
|
310
|
+
"""Wait for the server to be ready."""
|
|
311
|
+
start_time = time.time()
|
|
312
|
+
while time.time() - start_time < timeout:
|
|
313
|
+
try:
|
|
314
|
+
{%- if cookiecutter.is_a2a %}
|
|
315
|
+
response = requests.get(AGENT_CARD_URL, timeout=10)
|
|
316
|
+
{%- else %}
|
|
317
|
+
response = requests.get("http://127.0.0.1:8000/docs", timeout=10)
|
|
318
|
+
{%- endif %}
|
|
319
|
+
if response.status_code == 200:
|
|
320
|
+
logger.info("Server is ready")
|
|
321
|
+
return True
|
|
322
|
+
except RequestException:
|
|
323
|
+
pass
|
|
324
|
+
time.sleep(interval)
|
|
325
|
+
logger.error(f"Server did not become ready within {timeout} seconds")
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@pytest.fixture(scope="session")
|
|
330
|
+
def server_fixture(request: Any) -> Iterator[subprocess.Popen[str]]:
|
|
331
|
+
"""Pytest fixture to start and stop the server for testing."""
|
|
332
|
+
logger.info("Starting server process")
|
|
333
|
+
server_process = start_server()
|
|
334
|
+
if not wait_for_server():
|
|
335
|
+
pytest.fail("Server failed to start")
|
|
336
|
+
logger.info("Server process started")
|
|
337
|
+
|
|
338
|
+
def stop_server() -> None:
|
|
339
|
+
logger.info("Stopping server process")
|
|
340
|
+
server_process.terminate()
|
|
341
|
+
server_process.wait()
|
|
342
|
+
logger.info("Server process stopped")
|
|
343
|
+
|
|
344
|
+
request.addfinalizer(stop_server)
|
|
345
|
+
yield server_process
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
|
|
349
|
+
{%- if cookiecutter.is_a2a %}
|
|
350
|
+
"""Test the chat stream functionality using A2A JSON-RPC protocol."""
|
|
351
|
+
logger.info("Starting chat stream test")
|
|
352
|
+
|
|
353
|
+
message = Message(
|
|
354
|
+
message_id=f"msg-user-{uuid.uuid4()}",
|
|
355
|
+
role=Role.user,
|
|
356
|
+
parts=[Part(root=TextPart(text="What's the weather in San Francisco?"))],
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
request = SendStreamingMessageRequest(
|
|
360
|
+
id="test-req-001",
|
|
361
|
+
params=MessageSendParams(message=message),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Send the request
|
|
365
|
+
response = requests.post(
|
|
366
|
+
A2A_RPC_URL,
|
|
367
|
+
headers=HEADERS,
|
|
368
|
+
json=request.model_dump(mode="json", exclude_none=True),
|
|
369
|
+
stream=True,
|
|
370
|
+
timeout=60,
|
|
371
|
+
)
|
|
372
|
+
assert response.status_code == 200
|
|
373
|
+
|
|
374
|
+
# Parse streaming JSON-RPC responses
|
|
375
|
+
responses: list[SendStreamingMessageResponse] = []
|
|
376
|
+
|
|
377
|
+
for line in response.iter_lines():
|
|
378
|
+
if line:
|
|
379
|
+
line_str = line.decode("utf-8")
|
|
380
|
+
if line_str.startswith("data: "):
|
|
381
|
+
event_json = line_str[6:]
|
|
382
|
+
json_data = json.loads(event_json)
|
|
383
|
+
streaming_response = SendStreamingMessageResponse.model_validate(
|
|
384
|
+
json_data
|
|
385
|
+
)
|
|
386
|
+
responses.append(streaming_response)
|
|
387
|
+
|
|
388
|
+
assert responses, "No responses received from stream"
|
|
389
|
+
|
|
390
|
+
# Check for final status update
|
|
391
|
+
final_responses = [
|
|
392
|
+
r.root
|
|
393
|
+
for r in responses
|
|
394
|
+
if hasattr(r.root, "result")
|
|
395
|
+
and hasattr(r.root.result, "final")
|
|
396
|
+
and r.root.result.final is True
|
|
397
|
+
]
|
|
398
|
+
assert final_responses, "No final response received"
|
|
399
|
+
|
|
400
|
+
final_response = final_responses[-1]
|
|
401
|
+
assert final_response.result.kind == "status-update"
|
|
402
|
+
assert hasattr(final_response.result, "status")
|
|
403
|
+
assert final_response.result.status.state == "completed"
|
|
404
|
+
|
|
405
|
+
# Check for artifact content
|
|
406
|
+
artifact_responses = [
|
|
407
|
+
r.root
|
|
408
|
+
for r in responses
|
|
409
|
+
if hasattr(r.root, "result") and r.root.result.kind == "artifact-update"
|
|
410
|
+
]
|
|
411
|
+
assert artifact_responses, "No artifact content received in stream"
|
|
412
|
+
|
|
413
|
+
# Verify text content is in the artifact
|
|
414
|
+
artifact_response = artifact_responses[-1]
|
|
415
|
+
assert hasattr(artifact_response.result, "artifact")
|
|
416
|
+
artifact = artifact_response.result.artifact
|
|
417
|
+
assert artifact.parts, "Artifact has no parts"
|
|
418
|
+
|
|
419
|
+
has_text = any(
|
|
420
|
+
part.root.kind == "text" and hasattr(part.root, "text") and part.root.text
|
|
421
|
+
for part in artifact.parts
|
|
422
|
+
)
|
|
423
|
+
assert has_text, "No text content found in artifact"
|
|
424
|
+
{%- else %}
|
|
425
|
+
"""Test the chat stream functionality."""
|
|
426
|
+
logger.info("Starting chat stream test")
|
|
427
|
+
{% if cookiecutter.is_adk %}
|
|
428
|
+
# Create session first
|
|
429
|
+
user_id = "test_user_123"
|
|
430
|
+
session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
|
|
431
|
+
|
|
432
|
+
session_url = f"{BASE_URL}/apps/{{cookiecutter.agent_directory}}/users/{user_id}/sessions"
|
|
433
|
+
session_response = requests.post(
|
|
434
|
+
session_url,
|
|
435
|
+
headers=HEADERS,
|
|
436
|
+
json=session_data,
|
|
437
|
+
timeout=60,
|
|
438
|
+
)
|
|
439
|
+
assert session_response.status_code == 200
|
|
440
|
+
logger.info(f"Session creation response: {session_response.json()}")
|
|
441
|
+
session_id = session_response.json()["id"]
|
|
442
|
+
|
|
443
|
+
# Then send chat message
|
|
444
|
+
data = {
|
|
445
|
+
"app_name": "{{cookiecutter.agent_directory}}",
|
|
446
|
+
"user_id": user_id,
|
|
447
|
+
"session_id": session_id,
|
|
448
|
+
"new_message": {
|
|
449
|
+
"role": "user",
|
|
450
|
+
"parts": [{"text": "What's the weather in San Francisco?"}],
|
|
451
|
+
},
|
|
452
|
+
"streaming": True,
|
|
453
|
+
}
|
|
454
|
+
{% else %}
|
|
455
|
+
data = {
|
|
456
|
+
"input": {
|
|
457
|
+
"messages": [
|
|
458
|
+
{"type": "human", "content": "Hello, AI!"},
|
|
459
|
+
{"type": "ai", "content": "Hello!"},
|
|
460
|
+
{"type": "human", "content": "Who are you?"},
|
|
461
|
+
]
|
|
462
|
+
},
|
|
463
|
+
"config": {"metadata": {"user_id": "test-user", "session_id": "test-session"}},
|
|
464
|
+
}
|
|
465
|
+
{% endif %}
|
|
466
|
+
response = requests.post(
|
|
467
|
+
STREAM_URL, headers=HEADERS, json=data, stream=True, timeout=60
|
|
468
|
+
)
|
|
469
|
+
assert response.status_code == 200
|
|
470
|
+
|
|
471
|
+
{%- if cookiecutter.is_adk %}
|
|
472
|
+
# Parse SSE events from response
|
|
473
|
+
events = []
|
|
474
|
+
for line in response.iter_lines():
|
|
475
|
+
if line:
|
|
476
|
+
# SSE format is "data: {json}"
|
|
477
|
+
line_str = line.decode("utf-8")
|
|
478
|
+
if line_str.startswith("data: "):
|
|
479
|
+
event_json = line_str[6:] # Remove "data: " prefix
|
|
480
|
+
event = json.loads(event_json)
|
|
481
|
+
events.append(event)
|
|
482
|
+
|
|
483
|
+
assert events, "No events received from stream"
|
|
484
|
+
# Check for valid content in the response
|
|
485
|
+
has_text_content = False
|
|
486
|
+
for event in events:
|
|
487
|
+
content = event.get("content")
|
|
488
|
+
if (
|
|
489
|
+
content is not None
|
|
490
|
+
and content.get("parts")
|
|
491
|
+
and any(part.get("text") for part in content["parts"])
|
|
492
|
+
):
|
|
493
|
+
has_text_content = True
|
|
494
|
+
break
|
|
495
|
+
{%- else %}
|
|
496
|
+
events = [json.loads(line) for line in response.iter_lines() if line]
|
|
497
|
+
assert events, "No events received from stream"
|
|
498
|
+
|
|
499
|
+
# Verify each event is a tuple of message and metadata
|
|
500
|
+
for event in events:
|
|
501
|
+
assert isinstance(event, list), "Event should be a list"
|
|
502
|
+
assert len(event) == 2, "Event should contain message and metadata"
|
|
503
|
+
message, _ = event
|
|
504
|
+
|
|
505
|
+
# Verify message structure
|
|
506
|
+
assert isinstance(message, dict), "Message should be a dictionary"
|
|
507
|
+
assert message["type"] == "constructor"
|
|
508
|
+
assert "kwargs" in message, "Constructor message should have kwargs"
|
|
509
|
+
|
|
510
|
+
# Verify at least one message has content
|
|
511
|
+
has_content = False
|
|
512
|
+
for event in events:
|
|
513
|
+
message = event[0]
|
|
514
|
+
if message.get("type") == "constructor" and "content" in message["kwargs"]:
|
|
515
|
+
has_content = True
|
|
516
|
+
break
|
|
517
|
+
assert has_content, "At least one message should have content"
|
|
518
|
+
{%- endif %}
|
|
519
|
+
{%- endif %}
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
{%- if cookiecutter.is_a2a %}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def test_chat_non_streaming(server_fixture: subprocess.Popen[str]) -> None:
|
|
526
|
+
"""Test the non-streaming chat functionality using A2A JSON-RPC protocol."""
|
|
527
|
+
logger.info("Starting non-streaming chat test")
|
|
528
|
+
|
|
529
|
+
message = Message(
|
|
530
|
+
message_id=f"msg-user-{uuid.uuid4()}",
|
|
531
|
+
role=Role.user,
|
|
532
|
+
parts=[Part(root=TextPart(text="What's the weather in San Francisco?"))],
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
request = SendMessageRequest(
|
|
536
|
+
id="test-req-002",
|
|
537
|
+
params=MessageSendParams(message=message),
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
response = requests.post(
|
|
541
|
+
A2A_RPC_URL,
|
|
542
|
+
headers=HEADERS,
|
|
543
|
+
json=request.model_dump(mode="json", exclude_none=True),
|
|
544
|
+
timeout=60,
|
|
545
|
+
)
|
|
546
|
+
assert response.status_code == 200
|
|
547
|
+
|
|
548
|
+
# Parse the single JSON-RPC response
|
|
549
|
+
response_data = response.json()
|
|
550
|
+
message_response = SendMessageResponse.model_validate(response_data)
|
|
551
|
+
logger.info(f"Received response: {message_response}")
|
|
552
|
+
|
|
553
|
+
# For non-streaming, the result is a Task object
|
|
554
|
+
json_rpc_resp = message_response.root
|
|
555
|
+
assert hasattr(json_rpc_resp, "result")
|
|
556
|
+
task = json_rpc_resp.result
|
|
557
|
+
assert task.kind == "task"
|
|
558
|
+
assert hasattr(task, "status")
|
|
559
|
+
assert task.status.state == "completed"
|
|
560
|
+
|
|
561
|
+
# Check that we got artifacts (the final agent output)
|
|
562
|
+
assert hasattr(task, "artifacts")
|
|
563
|
+
assert task.artifacts, "No artifacts in task"
|
|
564
|
+
|
|
565
|
+
# Verify we got text content in the artifact
|
|
566
|
+
artifact = task.artifacts[0]
|
|
567
|
+
assert artifact.parts, "Artifact has no parts"
|
|
568
|
+
|
|
569
|
+
has_text = any(
|
|
570
|
+
part.root.kind == "text" and hasattr(part.root, "text") and part.root.text
|
|
571
|
+
for part in artifact.parts
|
|
572
|
+
)
|
|
573
|
+
assert has_text, "No text content found in artifact"
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def test_chat_stream_error_handling(server_fixture: subprocess.Popen[str]) -> None:
|
|
577
|
+
"""Test the chat stream error handling with invalid A2A request."""
|
|
578
|
+
logger.info("Starting chat stream error handling test")
|
|
579
|
+
|
|
580
|
+
invalid_data = {
|
|
581
|
+
"jsonrpc": "2.0",
|
|
582
|
+
"id": "test-error-001",
|
|
583
|
+
"method": "message/send",
|
|
584
|
+
"params": {
|
|
585
|
+
"message": {
|
|
586
|
+
"role": "user",
|
|
587
|
+
# Missing required 'parts' field
|
|
588
|
+
"messageId": f"msg-user-{uuid.uuid4()}",
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
response = requests.post(
|
|
594
|
+
A2A_RPC_URL, headers=HEADERS, json=invalid_data, timeout=10
|
|
595
|
+
)
|
|
596
|
+
assert response.status_code == 200
|
|
597
|
+
|
|
598
|
+
response_data = response.json()
|
|
599
|
+
error_response = JSONRPCErrorResponse.model_validate(response_data)
|
|
600
|
+
assert "error" in response_data, "Expected JSON-RPC error in response"
|
|
601
|
+
|
|
602
|
+
# Assert error for invalid parameters
|
|
603
|
+
assert error_response.error.code == -32602
|
|
604
|
+
|
|
605
|
+
logger.info("Error handling test completed successfully")
|
|
606
|
+
{%- else %}
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def test_chat_stream_error_handling(server_fixture: subprocess.Popen[str]) -> None:
|
|
610
|
+
"""Test the chat stream error handling."""
|
|
611
|
+
logger.info("Starting chat stream error handling test")
|
|
612
|
+
data = {
|
|
613
|
+
"input": {"messages": [{"type": "invalid_type", "content": "Cause an error"}]}
|
|
614
|
+
}
|
|
615
|
+
response = requests.post(
|
|
616
|
+
STREAM_URL, headers=HEADERS, json=data, stream=True, timeout=10
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
assert response.status_code == 422, (
|
|
620
|
+
f"Expected status code 422, got {response.status_code}"
|
|
621
|
+
)
|
|
622
|
+
logger.info("Error handling test completed successfully")
|
|
623
|
+
{%- endif %}
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def test_collect_feedback(server_fixture: subprocess.Popen[str]) -> None:
|
|
627
|
+
"""
|
|
628
|
+
Test the feedback collection endpoint (/feedback) to ensure it properly
|
|
629
|
+
logs the received feedback.
|
|
630
|
+
"""
|
|
631
|
+
# Create sample feedback data
|
|
632
|
+
feedback_data = {
|
|
633
|
+
"score": 4,
|
|
634
|
+
"user_id": "test-user-456",
|
|
635
|
+
"session_id": "test-session-456",
|
|
636
|
+
"text": "Great response!",
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
response = requests.post(
|
|
640
|
+
FEEDBACK_URL, json=feedback_data, headers=HEADERS, timeout=10
|
|
641
|
+
)
|
|
642
|
+
assert response.status_code == 200
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
{%- if cookiecutter.is_a2a %}
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def test_a2a_agent_json_generation(server_fixture: subprocess.Popen[str]) -> None:
|
|
649
|
+
"""
|
|
650
|
+
Test that the agent.json file is automatically generated and served correctly
|
|
651
|
+
via the well-known URI.
|
|
652
|
+
"""
|
|
653
|
+
# Verify the A2A endpoint serves the agent card
|
|
654
|
+
response = requests.get(AGENT_CARD_URL, timeout=10)
|
|
655
|
+
assert response.status_code == 200, f"A2A endpoint returned {response.status_code}"
|
|
656
|
+
|
|
657
|
+
# Validate required fields in served agent card
|
|
658
|
+
served_agent_card = response.json()
|
|
659
|
+
required_fields = [
|
|
660
|
+
"name",
|
|
661
|
+
"description",
|
|
662
|
+
"skills",
|
|
663
|
+
"capabilities",
|
|
664
|
+
"url",
|
|
665
|
+
"version",
|
|
666
|
+
]
|
|
667
|
+
for field in required_fields:
|
|
668
|
+
assert field in served_agent_card, (
|
|
669
|
+
f"Missing required field in served agent card: {field}"
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
{%- endif %}
|
|
674
|
+
{%- if cookiecutter.session_type == "agent_engine" %}
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
678
|
+
def cleanup_agent_engine_sessions() -> None:
|
|
679
|
+
"""Cleanup agent engine sessions created during tests."""
|
|
680
|
+
yield # Run tests first
|
|
681
|
+
|
|
682
|
+
# Cleanup after tests complete
|
|
683
|
+
from vertexai import agent_engines
|
|
684
|
+
|
|
685
|
+
try:
|
|
686
|
+
# Use same environment variable as server, default to project name
|
|
687
|
+
agent_name = os.environ.get(
|
|
688
|
+
"AGENT_ENGINE_SESSION_NAME", "{{cookiecutter.project_name}}"
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
# Find and delete agent engines with this name
|
|
692
|
+
existing_agents = list(agent_engines.list(filter=f"display_name={agent_name}"))
|
|
693
|
+
|
|
694
|
+
for agent_engine in existing_agents:
|
|
695
|
+
try:
|
|
696
|
+
agent_engines.delete(resource_name=agent_engine.name)
|
|
697
|
+
logger.info(f"Cleaned up agent engine: {agent_engine.name}")
|
|
698
|
+
except Exception as e:
|
|
699
|
+
logger.warning(
|
|
700
|
+
f"Failed to cleanup agent engine {agent_engine.name}: {e}"
|
|
701
|
+
)
|
|
702
|
+
except Exception as e:
|
|
703
|
+
logger.warning(f"Failed to cleanup agent engine sessions: {e}")
|
|
704
|
+
{%- endif %}
|
|
705
|
+
{%- endif %}
|