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.
- {agents → agent_starter_pack/agents}/adk_base/.template/templateconfig.yaml +1 -1
- {agents/live_api → agent_starter_pack/agents/adk_live}/.template/templateconfig.yaml +5 -7
- agent_starter_pack/agents/adk_live/README.md +32 -0
- agent_starter_pack/agents/adk_live/app/agent.py +48 -0
- agent_starter_pack/agents/adk_live/tests/unit/test_dummy.py +38 -0
- {agents → agent_starter_pack/agents}/agentic_rag/.template/templateconfig.yaml +1 -1
- agent_starter_pack/agents/crewai_coding_crew/app/agent.py +47 -0
- agent_starter_pack/agents/langgraph_base_react/app/agent.py +34 -0
- {src → agent_starter_pack}/base_template/GEMINI.md +1 -1
- {src → agent_starter_pack}/base_template/Makefile +130 -61
- {src → agent_starter_pack}/base_template/README.md +6 -6
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/apis.tf +1 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/log_sinks.tf +31 -25
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/providers.tf +1 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/variables.tf +1 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/github.tf +14 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/locals.tf +1 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/log_sinks.tf +37 -28
- {src → agent_starter_pack}/base_template/deployment/terraform/providers.tf +1 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/variables.tf +1 -1
- {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
- {src → agent_starter_pack}/base_template/pyproject.toml +22 -21
- {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +5 -5
- {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +3 -3
- {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +74 -11
- {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +6 -6
- {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/pr_checks.yaml +4 -4
- {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +95 -13
- {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +1 -1
- {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/typing.py +4 -4
- {src → agent_starter_pack}/cli/commands/setup_cicd.py +1 -1
- {src → agent_starter_pack}/cli/main.py +2 -2
- {src → agent_starter_pack}/cli/utils/gcp.py +1 -1
- {src → agent_starter_pack}/cli/utils/remote_template.py +12 -9
- {src → agent_starter_pack}/cli/utils/template.py +19 -15
- agent_starter_pack/deployment_targets/agent_engine/deployment/terraform/{% if not cookiecutter.is_adk_live %}service.tf{% else %}unused_service.tf{% endif %} +82 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +387 -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 +255 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +40 -14
- {src → agent_starter_pack}/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +13 -4
- 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
- {src → agent_starter_pack}/deployment_targets/cloud_run/Dockerfile +3 -3
- {src → agent_starter_pack}/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +4 -4
- {src → agent_starter_pack}/deployment_targets/cloud_run/deployment/terraform/service.tf +7 -7
- {src → agent_starter_pack}/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +207 -5
- {src → agent_starter_pack}/deployment_targets/cloud_run/tests/load_test/README.md +82 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +249 -0
- {src → agent_starter_pack}/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/server.py +190 -146
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package-lock.json +39 -1007
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package.json +1 -9
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.tsx +1 -1
- {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
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/side-panel/SidePanel.tsx +11 -5
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/side-panel/side-panel.scss +146 -115
- 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 +38 -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 +204 -23
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/utils.ts +27 -5
- {src → agent_starter_pack}/frontends/streamlit/frontend/utils/local_chat_history.py +2 -0
- {src → agent_starter_pack}/resources/docs/adk-cheatsheet.md +5 -5
- agent_starter_pack/resources/idx/.idx/dev.nix +64 -0
- agent_starter_pack/resources/idx/idx-template.json +6 -0
- {src → agent_starter_pack}/resources/idx/idx-template.nix +2 -3
- {src → agent_starter_pack}/resources/locks/uv-adk_base-agent_engine.lock +1079 -954
- {src → agent_starter_pack}/resources/locks/uv-adk_base-cloud_run.lock +1441 -1309
- agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +4229 -0
- agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +4822 -0
- {src → agent_starter_pack}/resources/locks/uv-agentic_rag-agent_engine.lock +1107 -997
- {src → agent_starter_pack}/resources/locks/uv-agentic_rag-cloud_run.lock +1485 -1368
- {src → agent_starter_pack}/resources/locks/uv-crewai_coding_crew-agent_engine.lock +1294 -1297
- {src → agent_starter_pack}/resources/locks/uv-crewai_coding_crew-cloud_run.lock +2028 -1807
- {src → agent_starter_pack}/resources/locks/uv-langgraph_base_react-agent_engine.lock +1176 -1197
- {src → agent_starter_pack}/resources/locks/uv-langgraph_base_react-cloud_run.lock +1947 -1679
- {src → agent_starter_pack}/utils/generate_locks.py +12 -7
- {src → agent_starter_pack}/utils/lock_utils.py +2 -2
- {src → agent_starter_pack}/utils/watch_and_rebuild.py +1 -1
- {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/METADATA +17 -18
- agent_starter_pack-0.17.0.dist-info/RECORD +179 -0
- agent_starter_pack-0.17.0.dist-info/entry_points.txt +2 -0
- llm.txt +1 -1
- agent_starter_pack-0.15.7.dist-info/RECORD +0 -176
- agent_starter_pack-0.15.7.dist-info/entry_points.txt +0 -2
- agents/crewai_coding_crew/app/agent.py +0 -86
- agents/langgraph_base_react/app/agent.py +0 -73
- agents/live_api/README.md +0 -37
- agents/live_api/app/agent.py +0 -72
- agents/live_api/tests/integration/test_server_e2e.py +0 -260
- agents/live_api/tests/load_test/load_test.py +0 -40
- agents/live_api/tests/unit/test_server.py +0 -144
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +0 -186
- src/deployment_targets/agent_engine/tests/load_test/README.md +0 -37
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +0 -126
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +0 -122
- src/resources/idx/.idx/dev.nix +0 -50
- src/resources/idx/idx-template.json +0 -21
- src/resources/locks/uv-live_api-cloud_run.lock +0 -6118
- {agents → agent_starter_pack/agents}/README.md +0 -0
- {agents → agent_starter_pack/agents}/adk_base/README.md +0 -0
- {agents → agent_starter_pack/agents}/adk_base/app/__init__.py +0 -0
- {agents → agent_starter_pack/agents}/adk_base/app/agent.py +0 -0
- {agents → agent_starter_pack/agents}/adk_base/notebooks/adk_app_testing.ipynb +0 -0
- {agents → agent_starter_pack/agents}/adk_base/notebooks/evaluating_adk_agent.ipynb +0 -0
- {agents → agent_starter_pack/agents}/adk_base/tests/integration/test_agent.py +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/README.md +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/__init__.py +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/agent.py +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/retrievers.py +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/templates.py +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/notebooks/adk_app_testing.ipynb +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/notebooks/evaluating_adk_agent.ipynb +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/tests/integration/test_agent.py +0 -0
- {agents → agent_starter_pack/agents}/crewai_coding_crew/.template/templateconfig.yaml +0 -0
- {agents → agent_starter_pack/agents}/crewai_coding_crew/README.md +0 -0
- {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/config/agents.yaml +0 -0
- {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/config/tasks.yaml +0 -0
- {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/crew.py +0 -0
- {agents → agent_starter_pack/agents}/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +0 -0
- {agents → agent_starter_pack/agents}/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +0 -0
- {agents → agent_starter_pack/agents}/crewai_coding_crew/tests/integration/test_agent.py +0 -0
- {agents → agent_starter_pack/agents}/langgraph_base_react/.template/templateconfig.yaml +0 -0
- {agents → agent_starter_pack/agents}/langgraph_base_react/README.md +0 -0
- {agents → agent_starter_pack/agents}/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +0 -0
- {agents → agent_starter_pack/agents}/langgraph_base_react/tests/integration/test_agent.py +0 -0
- {src → agent_starter_pack}/base_template/.gitignore +0 -0
- {src → agent_starter_pack}/base_template/deployment/README.md +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/apis.tf +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/iam.tf +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/storage.tf +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/vars/env.tfvars +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/iam.tf +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/service_accounts.tf +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/storage.tf +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/vars/env.tfvars +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +0 -0
- {src → agent_starter_pack}/base_template/tests/unit/test_dummy.py +0 -0
- {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/gcs.py +0 -0
- {src → agent_starter_pack}/cli/commands/create.py +0 -0
- {src → agent_starter_pack}/cli/commands/enhance.py +0 -0
- {src → agent_starter_pack}/cli/commands/list.py +0 -0
- {src → agent_starter_pack}/cli/utils/__init__.py +0 -0
- {src → agent_starter_pack}/cli/utils/cicd.py +0 -0
- {src → agent_starter_pack}/cli/utils/datastores.py +0 -0
- {src → agent_starter_pack}/cli/utils/logging.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/components/process_data.py +0 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/pipeline.py +0 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +0 -0
- {src → agent_starter_pack}/data_ingestion/pyproject.toml +0 -0
- {src → agent_starter_pack}/data_ingestion/uv.lock +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/deployment_metadata.json +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
- {src → agent_starter_pack}/deployment_targets/cloud_run/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
- {src → agent_starter_pack}/frontends/streamlit/frontend/side_bar.py +0 -0
- {src → agent_starter_pack}/frontends/streamlit/frontend/streamlit_app.py +0 -0
- {src → agent_starter_pack}/frontends/streamlit/frontend/style/app_markdown.py +0 -0
- {src → agent_starter_pack}/frontends/streamlit/frontend/utils/chat_utils.py +0 -0
- {src → agent_starter_pack}/frontends/streamlit/frontend/utils/message_editing.py +0 -0
- {src → agent_starter_pack}/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -0
- {src → agent_starter_pack}/frontends/streamlit/frontend/utils/stream_handler.py +0 -0
- {src → agent_starter_pack}/frontends/streamlit/frontend/utils/title_summary.py +0 -0
- {src → agent_starter_pack}/resources/containers/data_processing/Dockerfile +0 -0
- {src → agent_starter_pack}/resources/containers/e2e-tests/Dockerfile +0 -0
- {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/licenses/LICENSE +0 -0
{src → agent_starter_pack}/deployment_targets/cloud_run/tests/integration/test_server_e2e.py
RENAMED
|
@@ -11,6 +11,207 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
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}}.server: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
|
+
"invocation_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 %}
|
|
14
215
|
|
|
15
216
|
import json
|
|
16
217
|
import logging
|
|
@@ -32,7 +233,7 @@ logging.basicConfig(level=logging.INFO)
|
|
|
32
233
|
logger = logging.getLogger(__name__)
|
|
33
234
|
|
|
34
235
|
BASE_URL = "http://127.0.0.1:8000/"
|
|
35
|
-
{%- if
|
|
236
|
+
{%- if cookiecutter.is_adk %}
|
|
36
237
|
STREAM_URL = BASE_URL + "run_sse"
|
|
37
238
|
{%- else %}
|
|
38
239
|
STREAM_URL = BASE_URL + "stream_messages"
|
|
@@ -86,7 +287,7 @@ def start_server() -> subprocess.Popen[str]:
|
|
|
86
287
|
return process
|
|
87
288
|
|
|
88
289
|
|
|
89
|
-
def wait_for_server(timeout: int =
|
|
290
|
+
def wait_for_server(timeout: int = 90, interval: int = 1) -> bool:
|
|
90
291
|
"""Wait for the server to be ready."""
|
|
91
292
|
start_time = time.time()
|
|
92
293
|
while time.time() - start_time < timeout:
|
|
@@ -124,7 +325,7 @@ def server_fixture(request: Any) -> Iterator[subprocess.Popen[str]]:
|
|
|
124
325
|
def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
|
|
125
326
|
"""Test the chat stream functionality."""
|
|
126
327
|
logger.info("Starting chat stream test")
|
|
127
|
-
{% if
|
|
328
|
+
{% if cookiecutter.is_adk %}
|
|
128
329
|
# Create session first
|
|
129
330
|
user_id = "test_user_123"
|
|
130
331
|
session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
|
|
@@ -168,7 +369,7 @@ def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
|
|
|
168
369
|
)
|
|
169
370
|
assert response.status_code == 200
|
|
170
371
|
|
|
171
|
-
{%- if
|
|
372
|
+
{%- if cookiecutter.is_adk %}
|
|
172
373
|
# Parse SSE events from response
|
|
173
374
|
events = []
|
|
174
375
|
for line in response.iter_lines():
|
|
@@ -242,7 +443,7 @@ def test_collect_feedback(server_fixture: subprocess.Popen[str]) -> None:
|
|
|
242
443
|
# Create sample feedback data
|
|
243
444
|
feedback_data = {
|
|
244
445
|
"score": 4,
|
|
245
|
-
{%- if
|
|
446
|
+
{%- if cookiecutter.is_adk %}
|
|
246
447
|
"invocation_id": str(uuid.uuid4()),
|
|
247
448
|
{%- else %}
|
|
248
449
|
"run_id": str(uuid.uuid4()),
|
|
@@ -285,3 +486,4 @@ def cleanup_agent_engine_sessions() -> None:
|
|
|
285
486
|
except Exception as e:
|
|
286
487
|
logger.warning(f"Failed to cleanup agent engine sessions: {e}")
|
|
287
488
|
{%- endif %}
|
|
489
|
+
{%- endif %}
|
|
@@ -1,6 +1,87 @@
|
|
|
1
1
|
# Robust Load Testing for Generative AI Applications
|
|
2
2
|
|
|
3
3
|
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.
|
|
4
|
+
{%- if cookiecutter.agent_name == "adk_live" %}
|
|
5
|
+
|
|
6
|
+
## Local Load Testing
|
|
7
|
+
|
|
8
|
+
Follow these steps to execute load tests on your local machine:
|
|
9
|
+
|
|
10
|
+
**1. Start the FastAPI Server:**
|
|
11
|
+
|
|
12
|
+
Launch the FastAPI server in a separate terminal:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv run uvicorn {{cookiecutter.agent_directory}}.server:app --host 0.0.0.0 --port 8000 --reload
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**2. (In another tab) Create virtual environment with Locust**
|
|
19
|
+
Using another terminal tab, This is suggested to avoid conflicts with the existing application python environment.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python3 -m venv .locust_env && source .locust_env/bin/activate && pip install locust==2.31.1 websockets
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**3. Execute the Load Test:**
|
|
26
|
+
Trigger the Locust load test with the following command:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
locust -f tests/load_test/load_test.py \
|
|
30
|
+
-H http://127.0.0.1:8000 \
|
|
31
|
+
--headless \
|
|
32
|
+
-t 30s -u 2 -r 2 \
|
|
33
|
+
--csv=tests/load_test/.results/results \
|
|
34
|
+
--html=tests/load_test/.results/report.html
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This command initiates a 30-second load test, simulating 2 users spawning per second, reaching a maximum of 60 concurrent users.
|
|
38
|
+
|
|
39
|
+
**Results:**
|
|
40
|
+
|
|
41
|
+
Comprehensive CSV and HTML reports detailing the load test performance will be generated and saved in the `tests/load_test/.results` directory.
|
|
42
|
+
|
|
43
|
+
## Remote Load Testing (Targeting Cloud Run)
|
|
44
|
+
|
|
45
|
+
This framework also supports load testing against remote targets, such as a staging Cloud Run instance. This process is seamlessly integrated into the Continuous Delivery (CD) pipeline.
|
|
46
|
+
|
|
47
|
+
**Prerequisites:**
|
|
48
|
+
|
|
49
|
+
- **Dependencies:** Ensure your environment has the same dependencies required for local testing.
|
|
50
|
+
- **Cloud Run Invoker Role:** You'll need the `roles/run.invoker` role to invoke the Cloud Run service.
|
|
51
|
+
|
|
52
|
+
**Steps:**
|
|
53
|
+
|
|
54
|
+
**1. Start Cloud Run Proxy:**
|
|
55
|
+
|
|
56
|
+
Start the proxy in a separate terminal to expose your Cloud Run service on localhost. The proxy automatically handles IAM authentication:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
gcloud run services proxy YOUR_SERVICE_NAME --port=8080 --region us-central1 --quiet
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Replace `YOUR_SERVICE_NAME` with your Cloud Run service name. The `--quiet` flag auto-approves component installation prompts. You can optionally specify `--tag` to target a specific traffic tag.
|
|
63
|
+
|
|
64
|
+
**2. (In another tab) Create virtual environment with Locust:**
|
|
65
|
+
|
|
66
|
+
Using another terminal tab:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
python3 -m venv .locust_env && source .locust_env/bin/activate && pip install locust==2.31.1 websockets
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**3. Execute the Load Test:**
|
|
73
|
+
|
|
74
|
+
Execute load tests against the proxied service. The proxy handles authentication automatically:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
locust -f tests/load_test/load_test.py \
|
|
78
|
+
-H http://127.0.0.1:8080 \
|
|
79
|
+
--headless \
|
|
80
|
+
-t 30s -u 2 -r 2 \
|
|
81
|
+
--csv=tests/load_test/.results/results \
|
|
82
|
+
--html=tests/load_test/.results/report.html
|
|
83
|
+
```
|
|
84
|
+
{%- else %}
|
|
4
85
|
|
|
5
86
|
## Local Load Testing
|
|
6
87
|
|
|
@@ -81,3 +162,4 @@ locust -f tests/load_test/load_test.py \
|
|
|
81
162
|
--csv=tests/load_test/.results/results \
|
|
82
163
|
--html=tests/load_test/.results/report.html
|
|
83
164
|
```
|
|
165
|
+
{%- endif %}
|
|
@@ -0,0 +1,249 @@
|
|
|
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 json
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from locust import User, between, task
|
|
22
|
+
from websockets.exceptions import WebSocketException
|
|
23
|
+
from websockets.sync.client import connect
|
|
24
|
+
|
|
25
|
+
logging.basicConfig(level=logging.INFO)
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WebSocketUser(User):
|
|
30
|
+
"""Simulates a user making websocket requests to the remote agent engine."""
|
|
31
|
+
|
|
32
|
+
wait_time = between(1, 3) # Wait 1-3 seconds between tasks
|
|
33
|
+
abstract = True
|
|
34
|
+
|
|
35
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
self.ws_url = (
|
|
38
|
+
self.host.replace("http://", "ws://").replace("https://", "wss://") + "/ws"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@task
|
|
42
|
+
def websocket_audio_conversation(self) -> None:
|
|
43
|
+
"""Test a full websocket conversation with audio input."""
|
|
44
|
+
start_time = time.time()
|
|
45
|
+
response_count = 0
|
|
46
|
+
exception = None
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
response_count = self._websocket_interaction()
|
|
50
|
+
|
|
51
|
+
# Mark as failure if we got no valid responses
|
|
52
|
+
if response_count == 0:
|
|
53
|
+
exception = Exception("No responses received from agent")
|
|
54
|
+
|
|
55
|
+
except WebSocketException as e:
|
|
56
|
+
exception = e
|
|
57
|
+
logger.error(f"WebSocket error: {e}")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
exception = e
|
|
60
|
+
logger.error(f"Unexpected error: {e}")
|
|
61
|
+
finally:
|
|
62
|
+
total_time = int((time.time() - start_time) * 1000)
|
|
63
|
+
|
|
64
|
+
# Report the request metrics to Locust
|
|
65
|
+
self.environment.events.request.fire(
|
|
66
|
+
request_type="WS",
|
|
67
|
+
name="websocket_conversation",
|
|
68
|
+
response_time=total_time,
|
|
69
|
+
response_length=response_count * 100, # Approximate response size
|
|
70
|
+
response=None,
|
|
71
|
+
context={},
|
|
72
|
+
exception=exception,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def _websocket_interaction(self) -> int:
|
|
76
|
+
"""Handle the websocket interaction and return response count."""
|
|
77
|
+
response_count = 0
|
|
78
|
+
|
|
79
|
+
with connect(self.ws_url, open_timeout=10, close_timeout=20) as websocket:
|
|
80
|
+
# Wait for setupComplete
|
|
81
|
+
setup_response = websocket.recv(timeout=10.0)
|
|
82
|
+
setup_data = json.loads(setup_response)
|
|
83
|
+
assert "setupComplete" in setup_data, (
|
|
84
|
+
f"Expected setupComplete, got {setup_data}"
|
|
85
|
+
)
|
|
86
|
+
logger.info("Received setupComplete")
|
|
87
|
+
|
|
88
|
+
# Send dummy audio chunk with user_id
|
|
89
|
+
dummy_audio = bytes([0] * 1024)
|
|
90
|
+
audio_msg = {
|
|
91
|
+
"user_id": "load-test-user",
|
|
92
|
+
"realtimeInput": {
|
|
93
|
+
"mediaChunks": [
|
|
94
|
+
{
|
|
95
|
+
"mimeType": "audio/pcm;rate=16000",
|
|
96
|
+
"data": dummy_audio.hex(),
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
websocket.send(json.dumps(audio_msg))
|
|
102
|
+
logger.info("Sent audio chunk")
|
|
103
|
+
|
|
104
|
+
# Send text message to complete the turn
|
|
105
|
+
text_msg = {
|
|
106
|
+
"content": {
|
|
107
|
+
"role": "user",
|
|
108
|
+
"parts": [{"text": "Hello!"}],
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
websocket.send(json.dumps(text_msg))
|
|
112
|
+
logger.info("Sent text completion")
|
|
113
|
+
|
|
114
|
+
# Collect responses until turn_complete or timeout
|
|
115
|
+
for _ in range(20): # Max 20 responses
|
|
116
|
+
try:
|
|
117
|
+
response = websocket.recv(timeout=10.0)
|
|
118
|
+
response_data = json.loads(response)
|
|
119
|
+
response_count += 1
|
|
120
|
+
logger.debug(f"Received response: {response_data}")
|
|
121
|
+
|
|
122
|
+
if isinstance(response_data, dict) and response_data.get(
|
|
123
|
+
"turn_complete"
|
|
124
|
+
):
|
|
125
|
+
logger.info(f"Turn complete after {response_count} responses")
|
|
126
|
+
break
|
|
127
|
+
except TimeoutError:
|
|
128
|
+
logger.info(f"Timeout after {response_count} responses")
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
return response_count
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class RemoteAgentUser(WebSocketUser):
|
|
135
|
+
"""User for testing remote agent engine deployment."""
|
|
136
|
+
|
|
137
|
+
# Set the host via command line: locust -f load_test.py --host=https://your-deployed-service.run.app
|
|
138
|
+
host = "http://localhost:8000" # Default for local testing
|
|
139
|
+
{%- else %}
|
|
140
|
+
|
|
141
|
+
import os
|
|
142
|
+
import time
|
|
143
|
+
{%- if cookiecutter.is_adk %}
|
|
144
|
+
import uuid
|
|
145
|
+
|
|
146
|
+
import requests
|
|
147
|
+
from locust import HttpUser, between, task
|
|
148
|
+
{%- else %}
|
|
149
|
+
|
|
150
|
+
from locust import HttpUser, between, task
|
|
151
|
+
{%- endif %}
|
|
152
|
+
{% if cookiecutter.is_adk %}
|
|
153
|
+
ENDPOINT = "/run_sse"
|
|
154
|
+
{% else %}
|
|
155
|
+
ENDPOINT = "/stream_messages"
|
|
156
|
+
{% endif %}
|
|
157
|
+
|
|
158
|
+
class ChatStreamUser(HttpUser):
|
|
159
|
+
"""Simulates a user interacting with the chat stream API."""
|
|
160
|
+
|
|
161
|
+
wait_time = between(1, 3) # Wait 1-3 seconds between tasks
|
|
162
|
+
|
|
163
|
+
@task
|
|
164
|
+
def chat_stream(self) -> None:
|
|
165
|
+
"""Simulates a chat stream interaction."""
|
|
166
|
+
headers = {"Content-Type": "application/json"}
|
|
167
|
+
if os.environ.get("_ID_TOKEN"):
|
|
168
|
+
headers["Authorization"] = f"Bearer {os.environ['_ID_TOKEN']}"
|
|
169
|
+
{%- if cookiecutter.is_adk %}
|
|
170
|
+
# Create session first
|
|
171
|
+
user_id = f"user_{uuid.uuid4()}"
|
|
172
|
+
session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
|
|
173
|
+
|
|
174
|
+
session_url = f"{self.client.base_url}/apps/{{cookiecutter.agent_directory}}/users/{user_id}/sessions"
|
|
175
|
+
session_response = requests.post(
|
|
176
|
+
session_url,
|
|
177
|
+
headers=headers,
|
|
178
|
+
json=session_data,
|
|
179
|
+
timeout=10,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Get session_id from response
|
|
183
|
+
session_id = session_response.json()["id"]
|
|
184
|
+
|
|
185
|
+
# Send chat message
|
|
186
|
+
data = {
|
|
187
|
+
"app_name": "{{cookiecutter.agent_directory}}",
|
|
188
|
+
"user_id": user_id,
|
|
189
|
+
"session_id": session_id,
|
|
190
|
+
"new_message": {
|
|
191
|
+
"role": "user",
|
|
192
|
+
"parts": [{"text": "Hello! Weather in New york?"}],
|
|
193
|
+
},
|
|
194
|
+
"streaming": True,
|
|
195
|
+
}
|
|
196
|
+
{%- else %}
|
|
197
|
+
data = {
|
|
198
|
+
"input": {
|
|
199
|
+
"messages": [
|
|
200
|
+
{"type": "human", "content": "Hello, AI!"},
|
|
201
|
+
{"type": "ai", "content": "Hello!"},
|
|
202
|
+
{"type": "human", "content": "Who are you?"},
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
"config": {
|
|
206
|
+
"metadata": {"user_id": "test-user", "session_id": "test-session"}
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
{%- endif %}
|
|
210
|
+
start_time = time.time()
|
|
211
|
+
|
|
212
|
+
with self.client.post(
|
|
213
|
+
ENDPOINT,
|
|
214
|
+
name=f"{ENDPOINT} message",
|
|
215
|
+
headers=headers,
|
|
216
|
+
json=data,
|
|
217
|
+
catch_response=True,
|
|
218
|
+
stream=True,
|
|
219
|
+
params={"alt": "sse"},
|
|
220
|
+
) as response:
|
|
221
|
+
if response.status_code == 200:
|
|
222
|
+
events = []
|
|
223
|
+
for line in response.iter_lines():
|
|
224
|
+
if line:
|
|
225
|
+
line_str = line.decode("utf-8")
|
|
226
|
+
events.append(line_str)
|
|
227
|
+
|
|
228
|
+
if "429 Too Many Requests" in line_str:
|
|
229
|
+
self.environment.events.request.fire(
|
|
230
|
+
request_type="POST",
|
|
231
|
+
name=f"{ENDPOINT} rate_limited 429s",
|
|
232
|
+
response_time=0,
|
|
233
|
+
response_length=len(line),
|
|
234
|
+
response=response,
|
|
235
|
+
context={},
|
|
236
|
+
)
|
|
237
|
+
end_time = time.time()
|
|
238
|
+
total_time = end_time - start_time
|
|
239
|
+
self.environment.events.request.fire(
|
|
240
|
+
request_type="POST",
|
|
241
|
+
name=f"{ENDPOINT} end",
|
|
242
|
+
response_time=total_time * 1000, # Convert to milliseconds
|
|
243
|
+
response_length=len(events),
|
|
244
|
+
response=response,
|
|
245
|
+
context={},
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
response.failure(f"Unexpected status code: {response.status_code}")
|
|
249
|
+
{%- endif %}
|