agent-starter-pack 0.2.2__py3-none-any.whl → 0.3.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 (72) hide show
  1. {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/METADATA +14 -16
  2. {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/RECORD +69 -54
  3. agents/adk_base/README.md +14 -0
  4. agents/adk_base/app/agent.py +66 -0
  5. agents/adk_base/notebooks/adk_app_testing.ipynb +305 -0
  6. agents/adk_base/template/.templateconfig.yaml +21 -0
  7. agents/adk_base/tests/integration/test_agent.py +58 -0
  8. agents/agentic_rag/README.md +1 -0
  9. agents/agentic_rag/app/agent.py +44 -89
  10. agents/agentic_rag/app/templates.py +0 -25
  11. agents/agentic_rag/notebooks/adk_app_testing.ipynb +305 -0
  12. agents/agentic_rag/template/.templateconfig.yaml +3 -1
  13. agents/agentic_rag/tests/integration/test_agent.py +34 -27
  14. agents/langgraph_base_react/README.md +1 -1
  15. agents/langgraph_base_react/template/.templateconfig.yaml +1 -1
  16. src/base_template/Makefile +15 -4
  17. src/base_template/README.md +8 -2
  18. src/base_template/app/__init__.py +3 -0
  19. src/base_template/app/utils/tracing.py +11 -1
  20. src/base_template/app/utils/typing.py +54 -4
  21. src/base_template/deployment/README.md +4 -1
  22. src/base_template/deployment/cd/deploy-to-prod.yaml +3 -3
  23. src/base_template/deployment/cd/staging.yaml +4 -4
  24. src/base_template/deployment/ci/pr_checks.yaml +1 -1
  25. src/base_template/deployment/terraform/build_triggers.tf +3 -0
  26. src/base_template/deployment/terraform/dev/variables.tf +4 -0
  27. src/base_template/deployment/terraform/dev/vars/env.tfvars +0 -3
  28. src/base_template/deployment/terraform/variables.tf +4 -0
  29. src/base_template/deployment/terraform/vars/env.tfvars +0 -4
  30. src/base_template/pyproject.toml +5 -3
  31. src/{deployment_targets/agent_engine → base_template}/tests/unit/test_dummy.py +2 -1
  32. src/cli/commands/create.py +45 -11
  33. src/cli/commands/setup_cicd.py +25 -6
  34. src/cli/utils/gcp.py +1 -1
  35. src/cli/utils/template.py +27 -25
  36. src/data_ingestion/README.md +37 -50
  37. src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +2 -1
  38. src/deployment_targets/agent_engine/app/agent_engine_app.py +68 -22
  39. src/deployment_targets/agent_engine/app/utils/gcs.py +1 -1
  40. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +63 -0
  41. src/deployment_targets/agent_engine/tests/load_test/load_test.py +9 -2
  42. src/deployment_targets/cloud_run/Dockerfile +1 -1
  43. src/deployment_targets/cloud_run/app/server.py +41 -15
  44. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +60 -3
  45. src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
  46. src/deployment_targets/cloud_run/tests/load_test/load_test.py +57 -24
  47. src/frontends/live_api_react/frontend/package-lock.json +3 -3
  48. src/frontends/streamlit/frontend/utils/stream_handler.py +3 -3
  49. src/frontends/streamlit_adk/frontend/side_bar.py +214 -0
  50. src/frontends/streamlit_adk/frontend/streamlit_app.py +314 -0
  51. src/frontends/streamlit_adk/frontend/style/app_markdown.py +37 -0
  52. src/frontends/streamlit_adk/frontend/utils/chat_utils.py +84 -0
  53. src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +110 -0
  54. src/frontends/streamlit_adk/frontend/utils/message_editing.py +61 -0
  55. src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +223 -0
  56. src/frontends/streamlit_adk/frontend/utils/stream_handler.py +311 -0
  57. src/frontends/streamlit_adk/frontend/utils/title_summary.py +129 -0
  58. src/resources/locks/uv-adk_base-agent_engine.lock +5335 -0
  59. src/resources/locks/uv-adk_base-cloud_run.lock +5927 -0
  60. src/resources/locks/uv-agentic_rag-agent_engine.lock +939 -732
  61. src/resources/locks/uv-agentic_rag-cloud_run.lock +1087 -907
  62. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +778 -671
  63. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +852 -753
  64. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +665 -591
  65. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +842 -743
  66. src/resources/locks/uv-live_api-cloud_run.lock +830 -731
  67. agents/agentic_rag/notebooks/evaluating_langgraph_agent.ipynb +0 -1561
  68. src/base_template/tests/unit/test_utils/test_tracing_exporter.py +0 -140
  69. src/deployment_targets/cloud_run/tests/unit/test_server.py +0 -124
  70. {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/WHEEL +0 -0
  71. {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/entry_points.txt +0 -0
  72. {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,6 +12,53 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ # mypy: disable-error-code="attr-defined"
16
+ {%- if "adk" in cookiecutter.tags %}
17
+ import datetime
18
+ import json
19
+ import logging
20
+ from collections.abc import Mapping, Sequence
21
+ from typing import Any
22
+
23
+ import google.auth
24
+ import vertexai
25
+ from google.cloud import logging as google_cloud_logging
26
+ from opentelemetry import trace
27
+ from opentelemetry.sdk.trace import TracerProvider, export
28
+ from vertexai import agent_engines
29
+ from vertexai.preview.reasoning_engines import AdkApp
30
+
31
+ from app.agent import root_agent
32
+ from app.utils.gcs import create_bucket_if_not_exists
33
+ from app.utils.tracing import CloudTraceLoggingSpanExporter
34
+ from app.utils.typing import Feedback
35
+
36
+
37
+ class AgentEngineApp(AdkApp):
38
+ def set_up(self) -> None:
39
+ """Set up logging and tracing for the agent engine app."""
40
+ super().set_up()
41
+ logging_client = google_cloud_logging.Client()
42
+ self.logger = logging_client.logger(__name__)
43
+ provider = TracerProvider()
44
+ processor = export.BatchSpanProcessor(CloudTraceLoggingSpanExporter())
45
+ provider.add_span_processor(processor)
46
+ trace.set_tracer_provider(provider)
47
+
48
+ def register_feedback(self, feedback: dict[str, Any]) -> None:
49
+ """Collect and log feedback."""
50
+ feedback_obj = Feedback.model_validate(feedback)
51
+ self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
52
+
53
+ def register_operations(self) -> Mapping[str, Sequence]:
54
+ """Registers the operations of the Agent.
55
+
56
+ Extends the base operations to include feedback registration functionality.
57
+ """
58
+ operations = super().register_operations()
59
+ operations[""] = operations[""] + ["register_feedback"]
60
+ return operations
61
+ {%- else %}
15
62
  import datetime
16
63
  import json
17
64
  import logging
@@ -26,16 +73,12 @@ import vertexai
26
73
  from google.cloud import logging as google_cloud_logging
27
74
  from langchain_core.runnables import RunnableConfig
28
75
  from traceloop.sdk import Instruments, Traceloop
29
- from vertexai.preview import reasoning_engines
76
+ from vertexai import agent_engines
30
77
 
31
78
  from app.utils.gcs import create_bucket_if_not_exists
32
79
  from app.utils.tracing import CloudTraceLoggingSpanExporter
33
80
  from app.utils.typing import Feedback, InputChat, dumpd, ensure_valid_config
34
81
 
35
- logging.basicConfig(
36
- level=logging.INFO,
37
- )
38
-
39
82
 
40
83
  class AgentEngineApp:
41
84
  """Class for managing agent engine functionality."""
@@ -70,7 +113,6 @@ class AgentEngineApp:
70
113
  )
71
114
  except Exception as e:
72
115
  logging.error("Failed to initialize Telemetry: %s", str(e))
73
-
74
116
  self.runnable = agent
75
117
 
76
118
  # Add any additional variables here that should be included in the tracing logs
@@ -111,11 +153,6 @@ class AgentEngineApp:
111
153
  dumped_chunk = dumpd(chunk)
112
154
  yield dumped_chunk
113
155
 
114
- def register_feedback(self, feedback: dict[str, Any]) -> None:
115
- """Collect and log feedback."""
116
- feedback_obj = Feedback.model_validate(feedback)
117
- self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
118
-
119
156
  def query(
120
157
  self,
121
158
  *,
@@ -124,8 +161,15 @@ class AgentEngineApp:
124
161
  **kwargs: Any,
125
162
  ) -> Any:
126
163
  """Process a single input and return the agent's response."""
164
+ config = ensure_valid_config(config)
165
+ self.set_tracing_properties(config=config)
127
166
  return dumpd(self.runnable.invoke(input=input, config=config, **kwargs))
128
167
 
168
+ def register_feedback(self, feedback: dict[str, Any]) -> None:
169
+ """Collect and log feedback."""
170
+ feedback_obj = Feedback.model_validate(feedback)
171
+ self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
172
+
129
173
  def register_operations(self) -> Mapping[str, Sequence]:
130
174
  """Registers the operations of the Agent.
131
175
 
@@ -142,6 +186,7 @@ class AgentEngineApp:
142
186
  "": ["query", "register_feedback"],
143
187
  "stream": ["stream_query"],
144
188
  }
189
+ {%- endif %}
145
190
 
146
191
 
147
192
  def deploy_agent_engine_app(
@@ -151,7 +196,7 @@ def deploy_agent_engine_app(
151
196
  requirements_file: str = ".requirements.txt",
152
197
  extra_packages: list[str] = ["./app"],
153
198
  env_vars: dict[str, str] | None = None,
154
- ) -> reasoning_engines.ReasoningEngine:
199
+ ) -> agent_engines.AgentEngine:
155
200
  """Deploy the agent engine app to Vertex AI."""
156
201
 
157
202
  staging_bucket = f"gs://{project}-agent-engine"
@@ -164,24 +209,25 @@ def deploy_agent_engine_app(
164
209
  # Read requirements
165
210
  with open(requirements_file) as f:
166
211
  requirements = f.read().strip().split("\n")
167
-
168
- agent = AgentEngineApp(project_id=project, env_vars=env_vars)
169
-
212
+ {% if "adk" in cookiecutter.tags %}
213
+ agent_engine = AgentEngineApp(
214
+ agent=root_agent, env_vars=env_vars, enable_tracing=True
215
+ )
216
+ {% else %}
217
+ agent_engine = AgentEngineApp(project_id=project, env_vars=env_vars)
218
+ {% endif %}
170
219
  # Common configuration for both create and update operations
171
220
  agent_config = {
172
- "reasoning_engine": agent,
221
+ "agent_engine": agent_engine,
173
222
  "display_name": agent_name,
174
- "description": "This is a sample custom application in Agent Engine that uses LangGraph",
223
+ "description": "{{cookiecutter.agent_description}}",
175
224
  "extra_packages": extra_packages,
176
225
  }
177
226
  logging.info(f"Agent config: {agent_config}")
178
227
  agent_config["requirements"] = requirements
179
228
 
180
229
  # Check if an agent with this name already exists
181
- existing_agents = reasoning_engines.ReasoningEngine.list(
182
- filter=f"display_name={agent_name}"
183
- )
184
-
230
+ existing_agents = list(agent_engines.list(filter=f"display_name={agent_name}"))
185
231
  if existing_agents:
186
232
  # Update the existing agent with new configuration
187
233
  logging.info(f"Updating existing agent: {agent_name}")
@@ -189,7 +235,7 @@ def deploy_agent_engine_app(
189
235
  else:
190
236
  # Create a new agent if none exists
191
237
  logging.info(f"Creating new agent: {agent_name}")
192
- remote_agent = reasoning_engines.ReasoningEngine.create(**agent_config)
238
+ remote_agent = agent_engines.create(**agent_config)
193
239
 
194
240
  config = {
195
241
  "remote_agent_engine_id": remote_agent.resource_name,
@@ -14,8 +14,8 @@
14
14
 
15
15
  import logging
16
16
 
17
+ import google.cloud.storage as storage
17
18
  from google.api_core import exceptions
18
- from google.cloud import storage
19
19
 
20
20
 
21
21
  def create_bucket_if_not_exists(bucket_name: str, project: str, location: str) -> None:
@@ -15,18 +15,80 @@
15
15
  import logging
16
16
 
17
17
  import pytest
18
+ {%- if "adk" in cookiecutter.tags %}
19
+ from google.adk.events.event import Event
18
20
 
21
+ from app.agent import root_agent
19
22
  from app.agent_engine_app import AgentEngineApp
23
+ {%- else %}
24
+
25
+ from app.agent_engine_app import AgentEngineApp
26
+ {%- endif %}
20
27
 
21
28
 
22
29
  @pytest.fixture
23
30
  def agent_app() -> AgentEngineApp:
24
31
  """Fixture to create and set up AgentEngineApp instance"""
32
+ {%- if "adk" in cookiecutter.tags %}
33
+ app = AgentEngineApp(agent=root_agent)
34
+ {%- else %}
25
35
  app = AgentEngineApp()
36
+ {%- endif %}
26
37
  app.set_up()
27
38
  return app
28
39
 
40
+ {% if "adk" in cookiecutter.tags %}
41
+ def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
42
+ """
43
+ Integration test for the agent stream query functionality.
44
+ Tests that the agent returns valid streaming responses.
45
+ """
46
+ # Create message and events for the stream_query
47
+ message = "What's the weather in San Francisco?"
48
+ events = list(agent_app.stream_query(message=message, user_id="test"))
49
+ assert len(events) > 0, "Expected at least one chunk in response"
50
+
51
+ # Check for valid content in the response
52
+ has_text_content = False
53
+ for event in events:
54
+ validated_event = Event.model_validate(event)
55
+ content = validated_event.content
56
+ if (
57
+ content is not None
58
+ and content.parts
59
+ and any(part.text for part in content.parts)
60
+ ):
61
+ has_text_content = True
62
+ break
63
+
64
+ assert has_text_content, "Expected at least one event with text content"
29
65
 
66
+
67
+ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
68
+ """
69
+ Integration test for the agent feedback functionality.
70
+ Tests that feedback can be registered successfully.
71
+ """
72
+ feedback_data = {
73
+ "score": 5,
74
+ "text": "Great response!",
75
+ "invocation_id": "test-run-123",
76
+ }
77
+
78
+ # Should not raise any exceptions
79
+ agent_app.register_feedback(feedback_data)
80
+
81
+ # Test invalid feedback
82
+ with pytest.raises(ValueError):
83
+ invalid_feedback = {
84
+ "score": "invalid", # Score must be numeric
85
+ "text": "Bad feedback",
86
+ "invocation_id": "test-run-123",
87
+ }
88
+ agent_app.register_feedback(invalid_feedback)
89
+
90
+ logging.info("All assertions passed for agent feedback test")
91
+ {% else %}
30
92
  def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
31
93
  """
32
94
  Integration test for the agent stream query functionality.
@@ -118,3 +180,4 @@ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
118
180
  agent_app.register_feedback(invalid_feedback)
119
181
 
120
182
  logging.info("All assertions passed for agent feedback test")
183
+ {% endif %}
@@ -54,7 +54,14 @@ class ChatStreamUser(HttpUser):
54
54
  """Simulates a chat stream interaction."""
55
55
  headers = {"Content-Type": "application/json"}
56
56
  headers["Authorization"] = f"Bearer {os.environ['_AUTH_TOKEN']}"
57
-
57
+ {% if "adk" in cookiecutter.tags %}
58
+ data = {
59
+ "input": {
60
+ "message": "What's the weather in San Francisco?",
61
+ "user_id": "test",
62
+ }
63
+ }
64
+ {% else %}
58
65
  data = {
59
66
  "input": {
60
67
  "input": {
@@ -69,7 +76,7 @@ class ChatStreamUser(HttpUser):
69
76
  },
70
77
  }
71
78
  }
72
-
79
+ {% endif %}
73
80
  start_time = time.time()
74
81
  with self.client.post(
75
82
  url_path,
@@ -14,7 +14,7 @@
14
14
 
15
15
  FROM python:3.11-slim
16
16
 
17
- RUN pip install --no-cache-dir uv
17
+ RUN pip install --no-cache-dir uv==0.6.12
18
18
 
19
19
  WORKDIR /code
20
20
 
@@ -11,7 +11,32 @@
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 "adk" in cookiecutter.tags %}
15
+ import os
16
+
17
+ from fastapi import FastAPI
18
+ from google.adk.cli.fast_api import get_fast_api_app
19
+ from google.cloud import logging as google_cloud_logging
20
+ from opentelemetry import trace
21
+ from opentelemetry.sdk.trace import TracerProvider, export
22
+
23
+ from app.utils.tracing import CloudTraceLoggingSpanExporter
24
+ from app.utils.typing import Feedback
25
+
26
+ logging_client = google_cloud_logging.Client()
27
+ logger = logging_client.logger(__name__)
28
+
29
+ provider = TracerProvider()
30
+ processor = export.BatchSpanProcessor(CloudTraceLoggingSpanExporter())
31
+ provider.add_span_processor(processor)
32
+ trace.set_tracer_provider(provider)
14
33
 
34
+ AGENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
35
+ app: FastAPI = get_fast_api_app(agent_dir=AGENT_DIR, web=False)
36
+
37
+ app.title = "{{cookiecutter.project_name}}"
38
+ app.description = "API for interacting with the Agent {{cookiecutter.project_name}}"
39
+ {%- else %}
15
40
  import logging
16
41
  import os
17
42
  from collections.abc import Generator
@@ -40,7 +65,7 @@ try:
40
65
  app_name=app.title,
41
66
  disable_batch=False,
42
67
  exporter=CloudTraceLoggingSpanExporter(),
43
- instruments={% raw %}{{% endraw %}{%- for instrumentation in cookiecutter.otel_instrumentations %}{{ instrumentation }}{% if not loop.last %}, {% endif %}{%- endfor %}{% raw %}}{% endraw %},
68
+ instruments={Instruments.LANGCHAIN, Instruments.CREW},
44
69
  )
45
70
  except Exception as e:
46
71
  logging.error("Failed to initialize Telemetry: %s", str(e))
@@ -91,20 +116,6 @@ def redirect_root_to_docs() -> RedirectResponse:
91
116
  return RedirectResponse(url="/docs")
92
117
 
93
118
 
94
- @app.post("/feedback")
95
- def collect_feedback(feedback: Feedback) -> dict[str, str]:
96
- """Collect and log feedback.
97
-
98
- Args:
99
- feedback: The feedback data to log
100
-
101
- Returns:
102
- Success message
103
- """
104
- logger.log_struct(feedback.model_dump(), severity="INFO")
105
- return {"status": "success"}
106
-
107
-
108
119
  @app.post("/stream_messages")
109
120
  def stream_chat_events(request: Request) -> StreamingResponse:
110
121
  """Stream chat events in response to an input request.
@@ -119,6 +130,21 @@ def stream_chat_events(request: Request) -> StreamingResponse:
119
130
  stream_messages(input=request.input, config=request.config),
120
131
  media_type="text/event-stream",
121
132
  )
133
+ {%- endif %}
134
+
135
+
136
+ @app.post("/feedback")
137
+ def collect_feedback(feedback: Feedback) -> dict[str, str]:
138
+ """Collect and log feedback.
139
+
140
+ Args:
141
+ feedback: The feedback data to log
142
+
143
+ Returns:
144
+ Success message
145
+ """
146
+ logger.log_struct(feedback.model_dump(), severity="INFO")
147
+ return {"status": "success"}
122
148
 
123
149
 
124
150
  # Main execution
@@ -32,7 +32,11 @@ logging.basicConfig(level=logging.INFO)
32
32
  logger = logging.getLogger(__name__)
33
33
 
34
34
  BASE_URL = "http://127.0.0.1:8000/"
35
+ {%- if "adk" in cookiecutter.tags %}
36
+ STREAM_URL = BASE_URL + "run_sse"
37
+ {%- else %}
35
38
  STREAM_URL = BASE_URL + "stream_messages"
39
+ {%- endif %}
36
40
  FEEDBACK_URL = BASE_URL + "feedback"
37
41
 
38
42
  HEADERS = {"Content-Type": "application/json"}
@@ -116,23 +120,72 @@ def server_fixture(request: Any) -> Iterator[subprocess.Popen[str]]:
116
120
  def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
117
121
  """Test the chat stream functionality."""
118
122
  logger.info("Starting chat stream test")
123
+ {% if "adk" in cookiecutter.tags %}
124
+ # Create session first
125
+ user_id = "user_123"
126
+ session_id = "session_abc"
127
+ session_data = {"state": {"preferred_language": "English", "visit_count": 5}}
128
+ session_response = requests.post(
129
+ f"{BASE_URL}/apps/app/users/{user_id}/sessions/{session_id}",
130
+ headers=HEADERS,
131
+ json=session_data,
132
+ timeout=10,
133
+ )
134
+ assert session_response.status_code == 200
119
135
 
136
+ # Then send chat message
137
+ data = {
138
+ "app_name": "app",
139
+ "user_id": user_id,
140
+ "session_id": session_id,
141
+ "new_message": {
142
+ "role": "user",
143
+ "parts": [{"text": "What's the weather in San Francisco?"}],
144
+ },
145
+ "streaming": True,
146
+ }
147
+ {% else %}
120
148
  data = {
121
149
  "input": {
122
150
  "messages": [
123
151
  {"type": "human", "content": "Hello, AI!"},
124
152
  {"type": "ai", "content": "Hello!"},
125
- {"type": "human", "content": "What is the weather in NY?"},
153
+ {"type": "human", "content": "Who are you?"},
126
154
  ]
127
155
  },
128
156
  "config": {"metadata": {"user_id": "test-user", "session_id": "test-session"}},
129
157
  }
130
-
158
+ {% endif %}
131
159
  response = requests.post(
132
160
  STREAM_URL, headers=HEADERS, json=data, stream=True, timeout=10
133
161
  )
134
162
  assert response.status_code == 200
135
163
 
164
+ {%- if "adk" in cookiecutter.tags %}
165
+ # Parse SSE events from response
166
+ events = []
167
+ for line in response.iter_lines():
168
+ if line:
169
+ # SSE format is "data: {json}"
170
+ line_str = line.decode("utf-8")
171
+ if line_str.startswith("data: "):
172
+ event_json = line_str[6:] # Remove "data: " prefix
173
+ event = json.loads(event_json)
174
+ events.append(event)
175
+
176
+ assert events, "No events received from stream"
177
+ # Check for valid content in the response
178
+ has_text_content = False
179
+ for event in events:
180
+ content = event.get("content")
181
+ if (
182
+ content is not None
183
+ and content.get("parts")
184
+ and any(part.get("text") for part in content["parts"])
185
+ ):
186
+ has_text_content = True
187
+ break
188
+ {%- else %}
136
189
  events = [json.loads(line) for line in response.iter_lines() if line]
137
190
  assert events, "No events received from stream"
138
191
 
@@ -155,12 +208,12 @@ def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
155
208
  has_content = True
156
209
  break
157
210
  assert has_content, "At least one message should have content"
211
+ {%- endif %}
158
212
 
159
213
 
160
214
  def test_chat_stream_error_handling(server_fixture: subprocess.Popen[str]) -> None:
161
215
  """Test the chat stream error handling."""
162
216
  logger.info("Starting chat stream error handling test")
163
-
164
217
  data = {
165
218
  "input": {"messages": [{"type": "invalid_type", "content": "Cause an error"}]}
166
219
  }
@@ -182,7 +235,11 @@ def test_collect_feedback(server_fixture: subprocess.Popen[str]) -> None:
182
235
  # Create sample feedback data
183
236
  feedback_data = {
184
237
  "score": 4,
238
+ {%- if "adk" in cookiecutter.tags %}
239
+ "invocation_id": str(uuid.uuid4()),
240
+ {%- else %}
185
241
  "run_id": str(uuid.uuid4()),
242
+ {%- endif %}
186
243
  "text": "Great response!",
187
244
  }
188
245
 
@@ -28,7 +28,7 @@ Trigger the Locust load test with the following command:
28
28
  locust -f tests/load_test/load_test.py \
29
29
  -H http://127.0.0.1:8000 \
30
30
  --headless \
31
- -t 30s -u 60 -r 2 \
31
+ -t 30s -u 10 -r 2 \
32
32
  --csv=tests/load_test/.results/results \
33
33
  --html=tests/load_test/.results/report.html
34
34
  ```
@@ -15,9 +15,20 @@
15
15
  import json
16
16
  import os
17
17
  import time
18
+ {%- if "adk" in cookiecutter.tags %}
19
+ import uuid
18
20
 
21
+ import requests
19
22
  from locust import HttpUser, between, task
23
+ {%- else %}
20
24
 
25
+ from locust import HttpUser, between, task
26
+ {%- endif %}
27
+ {% if "adk" in cookiecutter.tags %}
28
+ ENDPOINT = "/run_sse"
29
+ {% else %}
30
+ ENDPOINT = "/stream_messages"
31
+ {% endif %}
21
32
 
22
33
  class ChatStreamUser(HttpUser):
23
34
  """Simulates a user interacting with the chat stream API."""
@@ -30,7 +41,30 @@ class ChatStreamUser(HttpUser):
30
41
  headers = {"Content-Type": "application/json"}
31
42
  if os.environ.get("_ID_TOKEN"):
32
43
  headers["Authorization"] = f"Bearer {os.environ['_ID_TOKEN']}"
44
+ {%- if "adk" in cookiecutter.tags %}
45
+ # Create session first
46
+ user_id = f"user_{uuid.uuid4()}"
47
+ session_id = f"session_{uuid.uuid4()}"
48
+ session_data = {"state": {"preferred_language": "English", "visit_count": 5}}
49
+ requests.post(
50
+ f"{self.client.base_url}/apps/app/users/{user_id}/sessions/{session_id}",
51
+ headers=headers,
52
+ json=session_data,
53
+ timeout=10,
54
+ )
33
55
 
56
+ # Send chat message
57
+ data = {
58
+ "app_name": "app",
59
+ "user_id": user_id,
60
+ "session_id": session_id,
61
+ "new_message": {
62
+ "role": "user",
63
+ "parts": [{"text": "What's the weather in San Francisco?"}],
64
+ },
65
+ "streaming": True,
66
+ }
67
+ {%- else %}
34
68
  data = {
35
69
  "input": {
36
70
  "messages": [
@@ -43,43 +77,42 @@ class ChatStreamUser(HttpUser):
43
77
  "metadata": {"user_id": "test-user", "session_id": "test-session"}
44
78
  },
45
79
  }
46
-
80
+ {%- endif %}
47
81
  start_time = time.time()
48
82
 
49
83
  with self.client.post(
50
- "/stream_messages",
84
+ ENDPOINT,
85
+ name=f"{ENDPOINT} message",
51
86
  headers=headers,
52
87
  json=data,
53
88
  catch_response=True,
54
- name="/stream_messages first message",
55
89
  stream=True,
90
+ params={"alt": "sse"},
56
91
  ) as response:
57
92
  if response.status_code == 200:
58
93
  events = []
59
94
  for line in response.iter_lines():
60
95
  if line:
96
+ {%- if "adk" in cookiecutter.tags %}
97
+ # SSE format is "data: {json}"
98
+ line_str = line.decode("utf-8")
99
+ if line_str.startswith("data: "):
100
+ event_json = line_str[6:] # Remove "data: " prefix
101
+ event = json.loads(event_json)
102
+ events.append(event)
103
+ {%- else %}
61
104
  event = json.loads(line)
62
105
  events.append(event)
63
- for chunk in event:
64
- if (
65
- isinstance(chunk, dict)
66
- and chunk.get("type") == "constructor"
67
- ):
68
- if not chunk.get("kwargs", {}).get("content"):
69
- continue
70
- response.success()
71
- end_time = time.time()
72
- total_time = end_time - start_time
73
- self.environment.events.request.fire(
74
- request_type="POST",
75
- name="/stream_messages end",
76
- response_time=total_time
77
- * 1000, # Convert to milliseconds
78
- response_length=len(json.dumps(events)),
79
- response=response,
80
- context={},
81
- )
82
- return
83
- response.failure("No valid response content received")
106
+ {%- endif %}
107
+ end_time = time.time()
108
+ total_time = end_time - start_time
109
+ self.environment.events.request.fire(
110
+ request_type="POST",
111
+ name=f"{ENDPOINT} end",
112
+ response_time=total_time * 1000, # Convert to milliseconds
113
+ response_length=len(json.dumps(events)),
114
+ response=response,
115
+ context={},
116
+ )
84
117
  else:
85
118
  response.failure(f"Unexpected status code: {response.status_code}")
@@ -2027,9 +2027,9 @@
2027
2027
  }
2028
2028
  },
2029
2029
  "node_modules/@babel/runtime": {
2030
- "version": "7.26.0",
2031
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
2032
- "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
2030
+ "version": "7.27.0",
2031
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
2032
+ "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
2033
2033
  "license": "MIT",
2034
2034
  "dependencies": {
2035
2035
  "regenerator-runtime": "^0.14.0"
@@ -28,7 +28,7 @@ import streamlit as st
28
28
  import vertexai
29
29
  from google.auth.exceptions import DefaultCredentialsError
30
30
  from langchain_core.messages import AIMessage, ToolMessage
31
- from vertexai.preview import reasoning_engines
31
+ from vertexai import agent_engines
32
32
 
33
33
  from frontend.utils.multimodal_utils import format_content
34
34
 
@@ -43,7 +43,7 @@ def get_remote_agent(remote_agent_engine_id: str) -> Any:
43
43
  project_id = parts[1]
44
44
  location = parts[3]
45
45
  vertexai.init(project=project_id, location=location)
46
- return reasoning_engines.ReasoningEngine(remote_agent_engine_id)
46
+ return agent_engines.AgentEngine(remote_agent_engine_id)
47
47
 
48
48
 
49
49
  @st.cache_resource
@@ -159,7 +159,7 @@ class Client:
159
159
  if self.authenticate_request:
160
160
  headers["Authorization"] = f"Bearer {self.id_token}"
161
161
  with requests.post(
162
- self.url, json=data, headers=headers, stream=True, timeout=10
162
+ self.url, json=data, headers=headers, stream=True, timeout=60
163
163
  ) as response:
164
164
  for line in response.iter_lines():
165
165
  if line: