agent-starter-pack 0.15.7__py3-none-any.whl → 0.17.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.

Potentially problematic release.


This version of agent-starter-pack might be problematic. Click here for more details.

Files changed (195) hide show
  1. {agents → agent_starter_pack/agents}/adk_base/.template/templateconfig.yaml +1 -1
  2. {agents/live_api → agent_starter_pack/agents/adk_live}/.template/templateconfig.yaml +5 -7
  3. agent_starter_pack/agents/adk_live/README.md +32 -0
  4. agent_starter_pack/agents/adk_live/app/agent.py +48 -0
  5. agent_starter_pack/agents/adk_live/tests/unit/test_dummy.py +38 -0
  6. {agents → agent_starter_pack/agents}/agentic_rag/.template/templateconfig.yaml +1 -1
  7. agent_starter_pack/agents/crewai_coding_crew/app/agent.py +47 -0
  8. agent_starter_pack/agents/langgraph_base_react/app/agent.py +34 -0
  9. {src → agent_starter_pack}/base_template/GEMINI.md +1 -1
  10. {src → agent_starter_pack}/base_template/Makefile +130 -61
  11. {src → agent_starter_pack}/base_template/README.md +6 -6
  12. {src → agent_starter_pack}/base_template/deployment/terraform/dev/apis.tf +1 -1
  13. {src → agent_starter_pack}/base_template/deployment/terraform/dev/log_sinks.tf +31 -25
  14. {src → agent_starter_pack}/base_template/deployment/terraform/dev/providers.tf +1 -1
  15. {src → agent_starter_pack}/base_template/deployment/terraform/dev/variables.tf +1 -1
  16. {src → agent_starter_pack}/base_template/deployment/terraform/github.tf +14 -0
  17. {src → agent_starter_pack}/base_template/deployment/terraform/locals.tf +1 -1
  18. {src → agent_starter_pack}/base_template/deployment/terraform/log_sinks.tf +37 -28
  19. {src → agent_starter_pack}/base_template/deployment/terraform/providers.tf +1 -1
  20. {src → agent_starter_pack}/base_template/deployment/terraform/variables.tf +1 -1
  21. {src → agent_starter_pack}/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +4 -2
  22. {src → agent_starter_pack}/base_template/pyproject.toml +22 -21
  23. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +5 -5
  24. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +3 -3
  25. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +74 -11
  26. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +6 -6
  27. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/pr_checks.yaml +4 -4
  28. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +95 -13
  29. {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +1 -1
  30. {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/typing.py +4 -4
  31. {src → agent_starter_pack}/cli/commands/setup_cicd.py +1 -1
  32. {src → agent_starter_pack}/cli/main.py +2 -2
  33. {src → agent_starter_pack}/cli/utils/gcp.py +1 -1
  34. {src → agent_starter_pack}/cli/utils/remote_template.py +12 -9
  35. {src → agent_starter_pack}/cli/utils/template.py +19 -15
  36. agent_starter_pack/deployment_targets/agent_engine/deployment/terraform/{% if not cookiecutter.is_adk_live %}service.tf{% else %}unused_service.tf{% endif %} +82 -0
  37. agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +387 -0
  38. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +84 -0
  39. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +255 -0
  40. {src → agent_starter_pack}/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +40 -14
  41. {src → agent_starter_pack}/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +13 -4
  42. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +520 -0
  43. {src → agent_starter_pack}/deployment_targets/cloud_run/Dockerfile +3 -3
  44. {src → agent_starter_pack}/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +4 -4
  45. {src → agent_starter_pack}/deployment_targets/cloud_run/deployment/terraform/service.tf +7 -7
  46. {src → agent_starter_pack}/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +207 -5
  47. {src → agent_starter_pack}/deployment_targets/cloud_run/tests/load_test/README.md +82 -0
  48. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +249 -0
  49. {src → agent_starter_pack}/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/server.py +190 -146
  50. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package-lock.json +39 -1007
  51. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package.json +1 -9
  52. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.tsx +1 -1
  53. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/Logger.tsx +8 -3
  54. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/logger.scss +26 -0
  55. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/side-panel/SidePanel.tsx +11 -5
  56. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/side-panel/side-panel.scss +146 -115
  57. agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/TranscriptionPreview.tsx +106 -0
  58. agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/transcription-preview.scss +150 -0
  59. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-live-api.ts +8 -2
  60. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/multimodal-live-types.ts +38 -2
  61. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-recorder.ts +1 -1
  62. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-streamer.ts +1 -1
  63. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/multimodal-live-client.ts +204 -23
  64. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/utils.ts +27 -5
  65. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/local_chat_history.py +2 -0
  66. {src → agent_starter_pack}/resources/docs/adk-cheatsheet.md +5 -5
  67. agent_starter_pack/resources/idx/.idx/dev.nix +64 -0
  68. agent_starter_pack/resources/idx/idx-template.json +6 -0
  69. {src → agent_starter_pack}/resources/idx/idx-template.nix +2 -3
  70. {src → agent_starter_pack}/resources/locks/uv-adk_base-agent_engine.lock +1079 -954
  71. {src → agent_starter_pack}/resources/locks/uv-adk_base-cloud_run.lock +1441 -1309
  72. agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +4229 -0
  73. agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +4822 -0
  74. {src → agent_starter_pack}/resources/locks/uv-agentic_rag-agent_engine.lock +1107 -997
  75. {src → agent_starter_pack}/resources/locks/uv-agentic_rag-cloud_run.lock +1485 -1368
  76. {src → agent_starter_pack}/resources/locks/uv-crewai_coding_crew-agent_engine.lock +1294 -1297
  77. {src → agent_starter_pack}/resources/locks/uv-crewai_coding_crew-cloud_run.lock +2028 -1807
  78. {src → agent_starter_pack}/resources/locks/uv-langgraph_base_react-agent_engine.lock +1176 -1197
  79. {src → agent_starter_pack}/resources/locks/uv-langgraph_base_react-cloud_run.lock +1947 -1679
  80. {src → agent_starter_pack}/utils/generate_locks.py +12 -7
  81. {src → agent_starter_pack}/utils/lock_utils.py +2 -2
  82. {src → agent_starter_pack}/utils/watch_and_rebuild.py +1 -1
  83. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/METADATA +17 -18
  84. agent_starter_pack-0.17.0.dist-info/RECORD +179 -0
  85. agent_starter_pack-0.17.0.dist-info/entry_points.txt +2 -0
  86. llm.txt +1 -1
  87. agent_starter_pack-0.15.7.dist-info/RECORD +0 -176
  88. agent_starter_pack-0.15.7.dist-info/entry_points.txt +0 -2
  89. agents/crewai_coding_crew/app/agent.py +0 -86
  90. agents/langgraph_base_react/app/agent.py +0 -73
  91. agents/live_api/README.md +0 -37
  92. agents/live_api/app/agent.py +0 -72
  93. agents/live_api/tests/integration/test_server_e2e.py +0 -260
  94. agents/live_api/tests/load_test/load_test.py +0 -40
  95. agents/live_api/tests/unit/test_server.py +0 -144
  96. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +0 -186
  97. src/deployment_targets/agent_engine/tests/load_test/README.md +0 -37
  98. src/deployment_targets/agent_engine/tests/load_test/load_test.py +0 -126
  99. src/deployment_targets/cloud_run/tests/load_test/load_test.py +0 -122
  100. src/resources/idx/.idx/dev.nix +0 -50
  101. src/resources/idx/idx-template.json +0 -21
  102. src/resources/locks/uv-live_api-cloud_run.lock +0 -6118
  103. {agents → agent_starter_pack/agents}/README.md +0 -0
  104. {agents → agent_starter_pack/agents}/adk_base/README.md +0 -0
  105. {agents → agent_starter_pack/agents}/adk_base/app/__init__.py +0 -0
  106. {agents → agent_starter_pack/agents}/adk_base/app/agent.py +0 -0
  107. {agents → agent_starter_pack/agents}/adk_base/notebooks/adk_app_testing.ipynb +0 -0
  108. {agents → agent_starter_pack/agents}/adk_base/notebooks/evaluating_adk_agent.ipynb +0 -0
  109. {agents → agent_starter_pack/agents}/adk_base/tests/integration/test_agent.py +0 -0
  110. {agents → agent_starter_pack/agents}/agentic_rag/README.md +0 -0
  111. {agents → agent_starter_pack/agents}/agentic_rag/app/__init__.py +0 -0
  112. {agents → agent_starter_pack/agents}/agentic_rag/app/agent.py +0 -0
  113. {agents → agent_starter_pack/agents}/agentic_rag/app/retrievers.py +0 -0
  114. {agents → agent_starter_pack/agents}/agentic_rag/app/templates.py +0 -0
  115. {agents → agent_starter_pack/agents}/agentic_rag/notebooks/adk_app_testing.ipynb +0 -0
  116. {agents → agent_starter_pack/agents}/agentic_rag/notebooks/evaluating_adk_agent.ipynb +0 -0
  117. {agents → agent_starter_pack/agents}/agentic_rag/tests/integration/test_agent.py +0 -0
  118. {agents → agent_starter_pack/agents}/crewai_coding_crew/.template/templateconfig.yaml +0 -0
  119. {agents → agent_starter_pack/agents}/crewai_coding_crew/README.md +0 -0
  120. {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/config/agents.yaml +0 -0
  121. {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/config/tasks.yaml +0 -0
  122. {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/crew.py +0 -0
  123. {agents → agent_starter_pack/agents}/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +0 -0
  124. {agents → agent_starter_pack/agents}/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +0 -0
  125. {agents → agent_starter_pack/agents}/crewai_coding_crew/tests/integration/test_agent.py +0 -0
  126. {agents → agent_starter_pack/agents}/langgraph_base_react/.template/templateconfig.yaml +0 -0
  127. {agents → agent_starter_pack/agents}/langgraph_base_react/README.md +0 -0
  128. {agents → agent_starter_pack/agents}/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +0 -0
  129. {agents → agent_starter_pack/agents}/langgraph_base_react/tests/integration/test_agent.py +0 -0
  130. {src → agent_starter_pack}/base_template/.gitignore +0 -0
  131. {src → agent_starter_pack}/base_template/deployment/README.md +0 -0
  132. {src → agent_starter_pack}/base_template/deployment/terraform/apis.tf +0 -0
  133. {src → agent_starter_pack}/base_template/deployment/terraform/dev/iam.tf +0 -0
  134. {src → agent_starter_pack}/base_template/deployment/terraform/dev/storage.tf +0 -0
  135. {src → agent_starter_pack}/base_template/deployment/terraform/dev/vars/env.tfvars +0 -0
  136. {src → agent_starter_pack}/base_template/deployment/terraform/iam.tf +0 -0
  137. {src → agent_starter_pack}/base_template/deployment/terraform/service_accounts.tf +0 -0
  138. {src → agent_starter_pack}/base_template/deployment/terraform/storage.tf +0 -0
  139. {src → agent_starter_pack}/base_template/deployment/terraform/vars/env.tfvars +0 -0
  140. {src → agent_starter_pack}/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +0 -0
  141. {src → agent_starter_pack}/base_template/tests/unit/test_dummy.py +0 -0
  142. {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/gcs.py +0 -0
  143. {src → agent_starter_pack}/cli/commands/create.py +0 -0
  144. {src → agent_starter_pack}/cli/commands/enhance.py +0 -0
  145. {src → agent_starter_pack}/cli/commands/list.py +0 -0
  146. {src → agent_starter_pack}/cli/utils/__init__.py +0 -0
  147. {src → agent_starter_pack}/cli/utils/cicd.py +0 -0
  148. {src → agent_starter_pack}/cli/utils/datastores.py +0 -0
  149. {src → agent_starter_pack}/cli/utils/logging.py +0 -0
  150. {src → agent_starter_pack}/cli/utils/version.py +0 -0
  151. {src → agent_starter_pack}/data_ingestion/README.md +0 -0
  152. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +0 -0
  153. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/process_data.py +0 -0
  154. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/pipeline.py +0 -0
  155. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +0 -0
  156. {src → agent_starter_pack}/data_ingestion/pyproject.toml +0 -0
  157. {src → agent_starter_pack}/data_ingestion/uv.lock +0 -0
  158. {src → agent_starter_pack}/deployment_targets/agent_engine/deployment_metadata.json +0 -0
  159. {src → agent_starter_pack}/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -0
  160. {src → agent_starter_pack}/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
  161. {src → agent_starter_pack}/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
  162. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/favicon.ico +0 -0
  163. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/index.html +0 -0
  164. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/robots.txt +0 -0
  165. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.scss +0 -0
  166. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.test.tsx +0 -0
  167. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/AudioPulse.tsx +0 -0
  168. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/audio-pulse.scss +0 -0
  169. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/mock-logs.ts +0 -0
  170. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/contexts/LiveAPIContext.tsx +0 -0
  171. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-media-stream-mux.ts +0 -0
  172. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-screen-capture.ts +0 -0
  173. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-webcam.ts +0 -0
  174. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.css +0 -0
  175. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.tsx +0 -0
  176. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/react-app-env.d.ts +0 -0
  177. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/reportWebVitals.ts +0 -0
  178. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/setupTests.ts +0 -0
  179. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audioworklet-registry.ts +0 -0
  180. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/store-logger.ts +0 -0
  181. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/audio-processing.ts +0 -0
  182. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/vol-meter.ts +0 -0
  183. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/tsconfig.json +0 -0
  184. {src → agent_starter_pack}/frontends/streamlit/frontend/side_bar.py +0 -0
  185. {src → agent_starter_pack}/frontends/streamlit/frontend/streamlit_app.py +0 -0
  186. {src → agent_starter_pack}/frontends/streamlit/frontend/style/app_markdown.py +0 -0
  187. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/chat_utils.py +0 -0
  188. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/message_editing.py +0 -0
  189. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -0
  190. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/stream_handler.py +0 -0
  191. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/title_summary.py +0 -0
  192. {src → agent_starter_pack}/resources/containers/data_processing/Dockerfile +0 -0
  193. {src → agent_starter_pack}/resources/containers/e2e-tests/Dockerfile +0 -0
  194. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/WHEEL +0 -0
  195. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,387 @@
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 using expose_app in local mode."""
46
+ command = [
47
+ sys.executable,
48
+ "-m",
49
+ "uvicorn",
50
+ "app.utils.expose_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
+ "run_id": "test-run-123",
206
+ "user_id": "test-user",
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 logging
217
+
218
+ import pytest
219
+ {%- if cookiecutter.is_adk %}
220
+ from google.adk.events.event import Event
221
+
222
+ from {{cookiecutter.agent_directory}}.agent import root_agent
223
+ from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
224
+ {%- else %}
225
+
226
+ from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
227
+ {%- endif %}
228
+
229
+
230
+ @pytest.fixture
231
+ def agent_app() -> AgentEngineApp:
232
+ """Fixture to create and set up AgentEngineApp instance"""
233
+ {%- if cookiecutter.is_adk %}
234
+ app = AgentEngineApp(agent=root_agent)
235
+ {%- else %}
236
+ app = AgentEngineApp()
237
+ {%- endif %}
238
+ app.set_up()
239
+ return app
240
+
241
+ {% if cookiecutter.is_adk %}
242
+ @pytest.mark.asyncio
243
+ async def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
244
+ """
245
+ Integration test for the agent stream query functionality.
246
+ Tests that the agent returns valid streaming responses.
247
+ """
248
+ # Create message and events for the async_stream_query
249
+ message = "What's the weather in San Francisco?"
250
+ events = []
251
+ async for event in agent_app.async_stream_query(message=message, user_id="test"):
252
+ events.append(event)
253
+ assert len(events) > 0, "Expected at least one chunk in response"
254
+
255
+ # Check for valid content in the response
256
+ has_text_content = False
257
+ for event in events:
258
+ validated_event = Event.model_validate(event)
259
+ content = validated_event.content
260
+ if (
261
+ content is not None
262
+ and content.parts
263
+ and any(part.text for part in content.parts)
264
+ ):
265
+ has_text_content = True
266
+ break
267
+
268
+ assert has_text_content, "Expected at least one event with text content"
269
+
270
+
271
+ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
272
+ """
273
+ Integration test for the agent feedback functionality.
274
+ Tests that feedback can be registered successfully.
275
+ """
276
+ feedback_data = {
277
+ "score": 5,
278
+ "text": "Great response!",
279
+ "invocation_id": "test-run-123",
280
+ }
281
+
282
+ # Should not raise any exceptions
283
+ agent_app.register_feedback(feedback_data)
284
+
285
+ # Test invalid feedback
286
+ with pytest.raises(ValueError):
287
+ invalid_feedback = {
288
+ "score": "invalid", # Score must be numeric
289
+ "text": "Bad feedback",
290
+ "invocation_id": "test-run-123",
291
+ }
292
+ agent_app.register_feedback(invalid_feedback)
293
+
294
+ logging.info("All assertions passed for agent feedback test")
295
+ {% else %}
296
+ def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
297
+ """
298
+ Integration test for the agent stream query functionality.
299
+ Tests that the agent returns valid streaming responses.
300
+ """
301
+ input_dict = {
302
+ "messages": [
303
+ {"type": "human", "content": "Test message"},
304
+ ],
305
+ "user_id": "test-user",
306
+ "session_id": "test-session",
307
+ }
308
+
309
+ events = list(agent_app.stream_query(input=input_dict))
310
+
311
+ assert len(events) > 0, "Expected at least one chunk in response"
312
+
313
+ # Verify each event is a tuple of message and metadata
314
+ for event in events:
315
+ assert isinstance(event, list), "Event should be a list"
316
+ assert len(event) == 2, "Event should contain message and metadata"
317
+ message, _ = event
318
+
319
+ # Verify message structure
320
+ assert isinstance(message, dict), "Message should be a dictionary"
321
+ assert message["type"] == "constructor"
322
+ assert "kwargs" in message, "Constructor message should have kwargs"
323
+
324
+ # Verify at least one message has content
325
+ has_content = False
326
+ for event in events:
327
+ message = event[0]
328
+ if message.get("type") == "constructor" and "content" in message["kwargs"]:
329
+ has_content = True
330
+ break
331
+ assert has_content, "At least one message should have content"
332
+
333
+
334
+ def test_agent_query(agent_app: AgentEngineApp) -> None:
335
+ """
336
+ Integration test for the agent query functionality.
337
+ Tests that the agent returns valid responses.
338
+ """
339
+ input_dict = {
340
+ "messages": [
341
+ {"type": "human", "content": "Test message"},
342
+ ],
343
+ "user_id": "test-user",
344
+ "session_id": "test-session",
345
+ }
346
+
347
+ response = agent_app.query(input=input_dict)
348
+
349
+ # Basic response validation
350
+ assert isinstance(response, dict), "Response should be a dictionary"
351
+ assert "messages" in response, "Response should contain messages"
352
+ assert len(response["messages"]) > 0, "Response should have at least one message"
353
+
354
+ # Validate last message is AI response with content
355
+ message = response["messages"][-1]
356
+ kwargs = message["kwargs"]
357
+ assert kwargs["type"] == "ai", "Last message should be AI response"
358
+ assert len(kwargs["content"]) > 0, "AI message content should not be empty"
359
+
360
+ logging.info("All assertions passed for agent query test")
361
+
362
+
363
+ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
364
+ """
365
+ Integration test for the agent feedback functionality.
366
+ Tests that feedback can be registered successfully.
367
+ """
368
+ feedback_data = {
369
+ "score": 5,
370
+ "text": "Great response!",
371
+ "run_id": "test-run-123",
372
+ }
373
+
374
+ # Should not raise any exceptions
375
+ agent_app.register_feedback(feedback_data)
376
+
377
+ # Test invalid feedback
378
+ with pytest.raises(ValueError):
379
+ invalid_feedback = {
380
+ "score": "invalid", # Score must be numeric
381
+ "text": "Bad feedback",
382
+ "run_id": "test-run-123",
383
+ }
384
+ agent_app.register_feedback(invalid_feedback)
385
+
386
+ logging.info("All assertions passed for agent feedback test")
387
+ {% endif %}{% endif %}
@@ -0,0 +1,84 @@
1
+ {%- if cookiecutter.agent_name == "adk_live" %}
2
+ # WebSocket Load Testing for Remote Agent Engine
3
+
4
+ This directory provides a comprehensive load testing framework for your Agent Engine application using WebSocket connections, leveraging the power of [Locust](http://locust.io), a leading open-source load testing tool.
5
+
6
+ The load test simulates realistic user interactions by:
7
+ - Establishing WebSocket connections
8
+ - Sending audio chunks in the proper `realtimeInput` format
9
+ - Sending text messages to complete turns
10
+ - Collecting and measuring responses until `turn_complete`
11
+
12
+ ## Load Testing with Remote Agent Engine
13
+
14
+ **1. Start the Expose App in Remote Mode:**
15
+
16
+ Launch the expose app server in a separate terminal, pointing to your deployed agent engine:
17
+
18
+ ```bash
19
+ uv run python -m app.utils.expose_app --mode remote --remote-id <your-agent-engine-id>
20
+ ```
21
+
22
+ Or if you have `deployment_metadata.json` in your project root:
23
+
24
+ ```bash
25
+ uv run python -m app.utils.expose_app --mode remote
26
+ ```
27
+
28
+ **2. Execute the Load Test:**
29
+
30
+ Using another terminal tab, trigger the Locust load test:
31
+
32
+ ```bash
33
+ uv run --with locust==2.31.1 --with websockets locust -f tests/load_test/load_test.py \
34
+ -H http://127.0.0.1:8000 \
35
+ --headless \
36
+ -t 30s -u 1 -r 1 \
37
+ --csv=tests/load_test/.results/results \
38
+ --html=tests/load_test/.results/report.html
39
+ ```
40
+
41
+ This command initiates a 30-second load test with 1 concurrent user.
42
+
43
+ **Results:**
44
+
45
+ Comprehensive CSV and HTML reports detailing the load test performance will be generated and saved in the `tests/load_test/.results` directory.
46
+ {%- else %}
47
+ # Robust Load Testing for Generative AI Applications
48
+
49
+ This directory provides a comprehensive load testing framework for your Generative AI application, leveraging the power of [Locust](http://locust.io), a leading open-source load testing tool.
50
+
51
+ ## Load Testing
52
+
53
+ Before running load tests, ensure you have deployed the backend remotely.
54
+
55
+ Follow these steps to execute load tests:
56
+
57
+ **1. Deploy the Backend Remotely:**
58
+ ```bash
59
+ gcloud config set project <your-dev-project-id>
60
+ make backend
61
+ ```
62
+
63
+ **2. Create a Virtual Environment for Locust:**
64
+ It's recommended to use a separate terminal tab and create a virtual environment for Locust to avoid conflicts with your application's Python environment.
65
+
66
+ ```bash
67
+ python3 -m venv .locust_env && source .locust_env/bin/activate && pip install locust==2.31.1
68
+ ```
69
+
70
+ **3. Execute the Load Test:**
71
+ Trigger the Locust load test with the following command:
72
+
73
+ ```bash
74
+ export _AUTH_TOKEN=$(gcloud auth print-access-token -q)
75
+ locust -f tests/load_test/load_test.py \
76
+ --headless \
77
+ -t 30s -u 5 -r 2 \
78
+ --csv=tests/load_test/.results/results \
79
+ --html=tests/load_test/.results/report.html
80
+ ```
81
+
82
+ This command initiates a 30-second load test, simulating 2 users spawning per second, reaching a maximum of 10 concurrent users.
83
+ {%- endif %}
84
+