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.
- {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/METADATA +14 -16
- {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/RECORD +69 -54
- agents/adk_base/README.md +14 -0
- agents/adk_base/app/agent.py +66 -0
- agents/adk_base/notebooks/adk_app_testing.ipynb +305 -0
- agents/adk_base/template/.templateconfig.yaml +21 -0
- agents/adk_base/tests/integration/test_agent.py +58 -0
- agents/agentic_rag/README.md +1 -0
- agents/agentic_rag/app/agent.py +44 -89
- agents/agentic_rag/app/templates.py +0 -25
- agents/agentic_rag/notebooks/adk_app_testing.ipynb +305 -0
- agents/agentic_rag/template/.templateconfig.yaml +3 -1
- agents/agentic_rag/tests/integration/test_agent.py +34 -27
- agents/langgraph_base_react/README.md +1 -1
- agents/langgraph_base_react/template/.templateconfig.yaml +1 -1
- src/base_template/Makefile +15 -4
- src/base_template/README.md +8 -2
- src/base_template/app/__init__.py +3 -0
- src/base_template/app/utils/tracing.py +11 -1
- src/base_template/app/utils/typing.py +54 -4
- src/base_template/deployment/README.md +4 -1
- src/base_template/deployment/cd/deploy-to-prod.yaml +3 -3
- src/base_template/deployment/cd/staging.yaml +4 -4
- src/base_template/deployment/ci/pr_checks.yaml +1 -1
- src/base_template/deployment/terraform/build_triggers.tf +3 -0
- src/base_template/deployment/terraform/dev/variables.tf +4 -0
- src/base_template/deployment/terraform/dev/vars/env.tfvars +0 -3
- src/base_template/deployment/terraform/variables.tf +4 -0
- src/base_template/deployment/terraform/vars/env.tfvars +0 -4
- src/base_template/pyproject.toml +5 -3
- src/{deployment_targets/agent_engine → base_template}/tests/unit/test_dummy.py +2 -1
- src/cli/commands/create.py +45 -11
- src/cli/commands/setup_cicd.py +25 -6
- src/cli/utils/gcp.py +1 -1
- src/cli/utils/template.py +27 -25
- src/data_ingestion/README.md +37 -50
- src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +2 -1
- src/deployment_targets/agent_engine/app/agent_engine_app.py +68 -22
- src/deployment_targets/agent_engine/app/utils/gcs.py +1 -1
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +63 -0
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +9 -2
- src/deployment_targets/cloud_run/Dockerfile +1 -1
- src/deployment_targets/cloud_run/app/server.py +41 -15
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +60 -3
- src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +57 -24
- src/frontends/live_api_react/frontend/package-lock.json +3 -3
- src/frontends/streamlit/frontend/utils/stream_handler.py +3 -3
- src/frontends/streamlit_adk/frontend/side_bar.py +214 -0
- src/frontends/streamlit_adk/frontend/streamlit_app.py +314 -0
- src/frontends/streamlit_adk/frontend/style/app_markdown.py +37 -0
- src/frontends/streamlit_adk/frontend/utils/chat_utils.py +84 -0
- src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +110 -0
- src/frontends/streamlit_adk/frontend/utils/message_editing.py +61 -0
- src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +223 -0
- src/frontends/streamlit_adk/frontend/utils/stream_handler.py +311 -0
- src/frontends/streamlit_adk/frontend/utils/title_summary.py +129 -0
- src/resources/locks/uv-adk_base-agent_engine.lock +5335 -0
- src/resources/locks/uv-adk_base-cloud_run.lock +5927 -0
- src/resources/locks/uv-agentic_rag-agent_engine.lock +939 -732
- src/resources/locks/uv-agentic_rag-cloud_run.lock +1087 -907
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +778 -671
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +852 -753
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +665 -591
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +842 -743
- src/resources/locks/uv-live_api-cloud_run.lock +830 -731
- agents/agentic_rag/notebooks/evaluating_langgraph_agent.ipynb +0 -1561
- src/base_template/tests/unit/test_utils/test_tracing_exporter.py +0 -140
- src/deployment_targets/cloud_run/tests/unit/test_server.py +0 -124
- {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.2.2.dist-info → agent_starter_pack-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# ADK Application Testing\n",
|
|
8
|
+
"\n",
|
|
9
|
+
"This notebook demonstrates how to test an ADK (Agent Development Kit) application.\n",
|
|
10
|
+
"It covers both local and remote testing, both with Agent Engine and Cloud Run.\n",
|
|
11
|
+
"\n",
|
|
12
|
+
"<img src=\"https://github.com/GoogleCloudPlatform/agent-starter-pack/blob/main/docs/images/adk_logo.png?raw=true\" width=\"400\">\n"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"cell_type": "markdown",
|
|
17
|
+
"metadata": {},
|
|
18
|
+
"source": [
|
|
19
|
+
"### Import libraries"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"cell_type": "code",
|
|
24
|
+
"execution_count": 1,
|
|
25
|
+
"metadata": {},
|
|
26
|
+
"outputs": [],
|
|
27
|
+
"source": [
|
|
28
|
+
"import json\n",
|
|
29
|
+
"\n",
|
|
30
|
+
"import requests\n",
|
|
31
|
+
"import vertexai.agent_engines"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"cell_type": "markdown",
|
|
36
|
+
"metadata": {},
|
|
37
|
+
"source": [
|
|
38
|
+
"## If you are using Agent Engine\n",
|
|
39
|
+
"See more documentation at [Agent Engine Overview](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview)"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"cell_type": "markdown",
|
|
44
|
+
"metadata": {},
|
|
45
|
+
"source": [
|
|
46
|
+
"### Local Testing\n",
|
|
47
|
+
"\n",
|
|
48
|
+
"You can import directly the AgentEngineApp class within your environment. "
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"cell_type": "code",
|
|
53
|
+
"execution_count": 2,
|
|
54
|
+
"metadata": {},
|
|
55
|
+
"outputs": [],
|
|
56
|
+
"source": [
|
|
57
|
+
"from app.agent import root_agent\n",
|
|
58
|
+
"from app.agent_engine_app import AgentEngineApp\n",
|
|
59
|
+
"\n",
|
|
60
|
+
"agent_engine = AgentEngineApp(agent=root_agent)"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"cell_type": "code",
|
|
65
|
+
"execution_count": null,
|
|
66
|
+
"metadata": {},
|
|
67
|
+
"outputs": [],
|
|
68
|
+
"source": [
|
|
69
|
+
"for event in agent_engine.stream_query(message=\"hi!\", user_id=\"test\"):\n",
|
|
70
|
+
" print(event)"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"cell_type": "markdown",
|
|
75
|
+
"metadata": {},
|
|
76
|
+
"source": [
|
|
77
|
+
"### Remote Testing"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"cell_type": "code",
|
|
82
|
+
"execution_count": 4,
|
|
83
|
+
"metadata": {},
|
|
84
|
+
"outputs": [],
|
|
85
|
+
"source": [
|
|
86
|
+
"# Replace with your Agent Engine ID\n",
|
|
87
|
+
"AGENT_ENGINE_ID = \"projects/PROJECT_ID/locations/us-central1/reasoningEngines/ENGINE_ID\""
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"cell_type": "code",
|
|
92
|
+
"execution_count": 5,
|
|
93
|
+
"metadata": {},
|
|
94
|
+
"outputs": [],
|
|
95
|
+
"source": [
|
|
96
|
+
"remote_agent_engine = vertexai.agent_engines.get(AGENT_ENGINE_ID)"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"cell_type": "code",
|
|
101
|
+
"execution_count": null,
|
|
102
|
+
"metadata": {},
|
|
103
|
+
"outputs": [],
|
|
104
|
+
"source": [
|
|
105
|
+
"for event in remote_agent_engine.stream_query(message=\"hi!\", user_id=\"test\"):\n",
|
|
106
|
+
" print(event)"
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"cell_type": "markdown",
|
|
111
|
+
"metadata": {},
|
|
112
|
+
"source": [
|
|
113
|
+
"## If you are using Cloud Run"
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"cell_type": "markdown",
|
|
118
|
+
"metadata": {},
|
|
119
|
+
"source": [
|
|
120
|
+
"### Local Testing\n",
|
|
121
|
+
"\n",
|
|
122
|
+
"> You can run the application locally via the `make backend` command."
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"cell_type": "markdown",
|
|
127
|
+
"metadata": {},
|
|
128
|
+
"source": [
|
|
129
|
+
"#### Create a session\n",
|
|
130
|
+
" Create a new session with user preferences and state information\n"
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"cell_type": "code",
|
|
135
|
+
"execution_count": null,
|
|
136
|
+
"metadata": {},
|
|
137
|
+
"outputs": [],
|
|
138
|
+
"source": [
|
|
139
|
+
"user_id = \"test_user_123\"\n",
|
|
140
|
+
"session_id = \"test_session_456\"\n",
|
|
141
|
+
"session_data = {\"state\": {\"preferred_language\": \"English\", \"visit_count\": 1}}\n",
|
|
142
|
+
"\n",
|
|
143
|
+
"session_url = f\"http://127.0.0.1:8000/apps/app/users/{user_id}/sessions/{session_id}\"\n",
|
|
144
|
+
"headers = {\"Content-Type\": \"application/json\"}\n",
|
|
145
|
+
"\n",
|
|
146
|
+
"session_response = requests.post(session_url, headers=headers, json=session_data)\n",
|
|
147
|
+
"print(f\"Session creation status code: {session_response.status_code}\")"
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"cell_type": "markdown",
|
|
152
|
+
"metadata": {},
|
|
153
|
+
"source": [
|
|
154
|
+
"#### Send a message\n",
|
|
155
|
+
"Send a message to the backend service and receive a streaming response\n"
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"cell_type": "code",
|
|
160
|
+
"execution_count": null,
|
|
161
|
+
"metadata": {},
|
|
162
|
+
"outputs": [],
|
|
163
|
+
"source": [
|
|
164
|
+
"message_data = {\n",
|
|
165
|
+
" \"app_name\": \"app\",\n",
|
|
166
|
+
" \"user_id\": user_id,\n",
|
|
167
|
+
" \"session_id\": session_id,\n",
|
|
168
|
+
" \"new_message\": {\"role\": \"user\", \"parts\": [{\"text\": \"Hello! Weather in New york?\"}]},\n",
|
|
169
|
+
" \"streaming\": True,\n",
|
|
170
|
+
"}\n",
|
|
171
|
+
"\n",
|
|
172
|
+
"message_url = \"http://127.0.0.1:8000/run_sse\"\n",
|
|
173
|
+
"message_response = requests.post(\n",
|
|
174
|
+
" message_url, headers=headers, json=message_data, stream=True\n",
|
|
175
|
+
")\n",
|
|
176
|
+
"\n",
|
|
177
|
+
"print(f\"Message send status code: {message_response.status_code}\")\n",
|
|
178
|
+
"\n",
|
|
179
|
+
"# Print streamed response\n",
|
|
180
|
+
"for line in message_response.iter_lines():\n",
|
|
181
|
+
" if line:\n",
|
|
182
|
+
" line_str = line.decode(\"utf-8\")\n",
|
|
183
|
+
" if line_str.startswith(\"data: \"):\n",
|
|
184
|
+
" event_json = line_str[6:]\n",
|
|
185
|
+
" event = json.loads(event_json)\n",
|
|
186
|
+
" print(f\"Received event: {event}\")"
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"cell_type": "markdown",
|
|
191
|
+
"metadata": {},
|
|
192
|
+
"source": [
|
|
193
|
+
"#### Remote Testing\n",
|
|
194
|
+
"\n",
|
|
195
|
+
"For more information about authenticating HTTPS requests to Cloud Run services, see:\n",
|
|
196
|
+
"[Cloud Run Authentication Documentation](https://cloud.google.com/run/docs/triggering/https-request)\n",
|
|
197
|
+
"\n",
|
|
198
|
+
"Remote testing involves using a deployed service URL instead of localhost.\n",
|
|
199
|
+
"\n",
|
|
200
|
+
"Authentication is handled using GCP identity tokens instead of local credentials."
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"cell_type": "code",
|
|
205
|
+
"execution_count": 20,
|
|
206
|
+
"metadata": {},
|
|
207
|
+
"outputs": [],
|
|
208
|
+
"source": [
|
|
209
|
+
"ID_TOKEN = get_ipython().getoutput(\"gcloud auth print-identity-token -q\")[0]"
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"cell_type": "code",
|
|
214
|
+
"execution_count": null,
|
|
215
|
+
"metadata": {},
|
|
216
|
+
"outputs": [],
|
|
217
|
+
"source": [
|
|
218
|
+
"SERVICE_URL = \"YOUR_SERVICE_URL_HERE\" # Replace with your Cloud Run service URL"
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"cell_type": "markdown",
|
|
223
|
+
"metadata": {},
|
|
224
|
+
"source": [
|
|
225
|
+
"You'll need to first create a Session"
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"cell_type": "code",
|
|
230
|
+
"execution_count": null,
|
|
231
|
+
"metadata": {},
|
|
232
|
+
"outputs": [],
|
|
233
|
+
"source": [
|
|
234
|
+
"user_id = \"test_user_123\"\n",
|
|
235
|
+
"session_id = \"test_session_456\"\n",
|
|
236
|
+
"session_data = {\"state\": {\"preferred_language\": \"English\", \"visit_count\": 1}}\n",
|
|
237
|
+
"\n",
|
|
238
|
+
"session_url = f\"{SERVICE_URL}/apps/app/users/{user_id}/sessions/{session_id}\"\n",
|
|
239
|
+
"headers = {\"Content-Type\": \"application/json\", \"Authorization\": f\"Bearer {ID_TOKEN}\"}\n",
|
|
240
|
+
"\n",
|
|
241
|
+
"session_response = requests.post(session_url, headers=headers, json=session_data)\n",
|
|
242
|
+
"print(f\"Session creation status code: {session_response.status_code}\")"
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"cell_type": "markdown",
|
|
247
|
+
"metadata": {},
|
|
248
|
+
"source": [
|
|
249
|
+
"Then you will be able to send a message"
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"cell_type": "code",
|
|
254
|
+
"execution_count": null,
|
|
255
|
+
"metadata": {},
|
|
256
|
+
"outputs": [],
|
|
257
|
+
"source": [
|
|
258
|
+
"message_data = {\n",
|
|
259
|
+
" \"app_name\": \"app\",\n",
|
|
260
|
+
" \"user_id\": user_id,\n",
|
|
261
|
+
" \"session_id\": session_id,\n",
|
|
262
|
+
" \"new_message\": {\"role\": \"user\", \"parts\": [{\"text\": \"Hello! Weather in New york?\"}]},\n",
|
|
263
|
+
" \"streaming\": True,\n",
|
|
264
|
+
"}\n",
|
|
265
|
+
"\n",
|
|
266
|
+
"message_url = f\"{SERVICE_URL}/run_sse\"\n",
|
|
267
|
+
"message_response = requests.post(\n",
|
|
268
|
+
" message_url, headers=headers, json=message_data, stream=True\n",
|
|
269
|
+
")\n",
|
|
270
|
+
"\n",
|
|
271
|
+
"print(f\"Message send status code: {message_response.status_code}\")\n",
|
|
272
|
+
"\n",
|
|
273
|
+
"# Print streamed response\n",
|
|
274
|
+
"for line in message_response.iter_lines():\n",
|
|
275
|
+
" if line:\n",
|
|
276
|
+
" line_str = line.decode(\"utf-8\")\n",
|
|
277
|
+
" if line_str.startswith(\"data: \"):\n",
|
|
278
|
+
" event_json = line_str[6:]\n",
|
|
279
|
+
" event = json.loads(event_json)\n",
|
|
280
|
+
" print(f\"Received event: {event}\")"
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
"metadata": {
|
|
285
|
+
"kernelspec": {
|
|
286
|
+
"display_name": ".venv",
|
|
287
|
+
"language": "python",
|
|
288
|
+
"name": "python3"
|
|
289
|
+
},
|
|
290
|
+
"language_info": {
|
|
291
|
+
"codemirror_mode": {
|
|
292
|
+
"name": "ipython",
|
|
293
|
+
"version": 3
|
|
294
|
+
},
|
|
295
|
+
"file_extension": ".py",
|
|
296
|
+
"mimetype": "text/x-python",
|
|
297
|
+
"name": "python",
|
|
298
|
+
"nbconvert_exporter": "python",
|
|
299
|
+
"pygments_lexer": "ipython3",
|
|
300
|
+
"version": "3.12.8"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
"nbformat": 4,
|
|
304
|
+
"nbformat_minor": 2
|
|
305
|
+
}
|
|
@@ -17,10 +17,12 @@ settings:
|
|
|
17
17
|
requires_data_ingestion: true
|
|
18
18
|
deployment_targets: ["agent_engine", "cloud_run"]
|
|
19
19
|
extra_dependencies: [
|
|
20
|
+
"google-adk~=0.1.0",
|
|
20
21
|
"langchain-google-vertexai~=2.0.7",
|
|
21
22
|
"langchain~=0.3.14",
|
|
22
|
-
"langgraph~=0.3.21",
|
|
23
23
|
"langchain-community~=0.3.17",
|
|
24
24
|
"langchain-openai~=0.3.5",
|
|
25
25
|
"langchain-google-community[vertexaisearch]~=2.0.7",
|
|
26
26
|
]
|
|
27
|
+
tags: ["adk"]
|
|
28
|
+
frontend_type: "streamlit_adk"
|
|
@@ -15,43 +15,50 @@
|
|
|
15
15
|
# mypy: disable-error-code="union-attr"
|
|
16
16
|
from unittest.mock import MagicMock, patch
|
|
17
17
|
|
|
18
|
-
from
|
|
18
|
+
from google.adk.agents.run_config import RunConfig, StreamingMode
|
|
19
|
+
from google.adk.runners import Runner
|
|
20
|
+
from google.adk.sessions import InMemorySessionService
|
|
21
|
+
from google.genai import types
|
|
22
|
+
|
|
23
|
+
from app.agent import root_agent
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
@patch(
|
|
22
|
-
"app.agent.retrieve_docs
|
|
23
|
-
return_value=
|
|
27
|
+
"app.agent.retrieve_docs",
|
|
28
|
+
return_value="dummy content",
|
|
24
29
|
)
|
|
25
30
|
def test_agent_stream(mock_retrieve: MagicMock) -> None:
|
|
26
31
|
"""
|
|
27
32
|
Integration test for the agent stream functionality.
|
|
28
33
|
Tests that the agent returns valid streaming responses.
|
|
29
34
|
"""
|
|
30
|
-
input_dict = {
|
|
31
|
-
"messages": [
|
|
32
|
-
{"type": "human", "content": "Hi"},
|
|
33
|
-
{"type": "ai", "content": "Hi there!"},
|
|
34
|
-
{
|
|
35
|
-
"type": "human",
|
|
36
|
-
"content": "How to split a string with pattern 'alphabet/alphabet' and not split 'number/number' in same string",
|
|
37
|
-
},
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
events = [
|
|
42
|
-
message for message, _ in agent.stream(input_dict, stream_mode="messages")
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
# Verify we get a reasonable number of messages
|
|
46
|
-
assert len(events) > 0, "Expected at least one message"
|
|
47
35
|
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
session_service = InMemorySessionService()
|
|
37
|
+
|
|
38
|
+
session = session_service.create_session(user_id="test_user", app_name="test")
|
|
39
|
+
runner = Runner(agent=root_agent, session_service=session_service, app_name="test")
|
|
40
|
+
|
|
41
|
+
message = types.Content(
|
|
42
|
+
role="user", parts=[types.Part.from_text(text="Why is the sky blue?")]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
events = list(
|
|
46
|
+
runner.run(
|
|
47
|
+
new_message=message,
|
|
48
|
+
user_id="test_user",
|
|
49
|
+
session_id=session.id,
|
|
50
|
+
run_config=RunConfig(streaming_mode=StreamingMode.SSE),
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
assert len(events) > 0, "Expected at least one message"
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
has_content = False
|
|
55
|
+
has_text_content = False
|
|
53
56
|
for event in events:
|
|
54
|
-
if
|
|
55
|
-
|
|
57
|
+
if (
|
|
58
|
+
event.content
|
|
59
|
+
and event.content.parts
|
|
60
|
+
and any(part.text for part in event.content.parts)
|
|
61
|
+
):
|
|
62
|
+
has_text_content = True
|
|
56
63
|
break
|
|
57
|
-
assert
|
|
64
|
+
assert has_text_content, "Expected at least one message with text content"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# LangGraph Base React Agent
|
|
2
2
|
|
|
3
|
-
A minimal example demonstrating how to build a
|
|
3
|
+
A minimal example demonstrating how to build a ReAct agent using LangGraph. This agent serves as an excellent starting point for developers looking to implement LangGraph-based solutions.
|
|
4
4
|
|
|
5
5
|
## Key Features
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
description: "
|
|
15
|
+
description: "An agent implementing a base ReAct agent using LangGraph"
|
|
16
16
|
settings:
|
|
17
17
|
requires_data_ingestion: false
|
|
18
18
|
deployment_targets: ["agent_engine", "cloud_run"]
|
src/base_template/Makefile
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
install:
|
|
2
|
-
@command -v uv >/dev/null 2>&1 || { echo "uv is not installed. Installing uv..."; curl -LsSf https://astral.sh/uv/install.sh | sh; source ~/.bashrc; }
|
|
2
|
+
@command -v uv >/dev/null 2>&1 || { echo "uv is not installed. Installing uv..."; curl -LsSf https://astral.sh/uv/0.6.12/install.sh | sh; source ~/.bashrc; }
|
|
3
3
|
uv sync --dev {% if cookiecutter.agent_name != 'live_api' %}--extra streamlit{%- endif %} --extra jupyter --frozen{% if cookiecutter.agent_name == 'live_api' %} && npm --prefix frontend install{%- endif %}
|
|
4
4
|
|
|
5
5
|
test:
|
|
6
6
|
uv run pytest tests/unit && uv run pytest tests/integration
|
|
7
7
|
|
|
8
8
|
playground:
|
|
9
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
10
|
+
@echo "+-----------------------------------------------------------------------------+"
|
|
11
|
+
@echo "| Starting ADK Web Server via 'adk web' command. |"
|
|
12
|
+
@echo "| |"
|
|
13
|
+
@echo "| Please select the app folder to start interacting with your agent. |"
|
|
14
|
+
@echo "+-----------------------------------------------------------------------------+"
|
|
15
|
+
uv run adk web
|
|
16
|
+
{%- else %}
|
|
9
17
|
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
10
18
|
uv run uvicorn app.server:app --host 0.0.0.0 --port 8000 --reload &
|
|
11
19
|
{%- endif %}
|
|
@@ -14,12 +22,15 @@ playground:
|
|
|
14
22
|
{%- else %}
|
|
15
23
|
{% if cookiecutter.deployment_target == 'agent_engine' %}PYTHONPATH=. {% endif %}uv run streamlit run frontend/streamlit_app.py --browser.serverAddress=localhost --server.enableCORS=false --server.enableXsrfProtection=false
|
|
16
24
|
{%- endif %}
|
|
25
|
+
{%- endif %}
|
|
17
26
|
|
|
18
27
|
backend:
|
|
19
28
|
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
20
29
|
uv run uvicorn app.server:app --host 0.0.0.0 --port 8000 --reload
|
|
21
30
|
{%- elif cookiecutter.deployment_target == 'agent_engine' %}
|
|
22
|
-
uv export
|
|
31
|
+
# Export dependencies to requirements file using uv export (preferred method), otherwise fall back to uv pip freeze
|
|
32
|
+
uv export --no-hashes --no-sources --no-header --no-dev --no-emit-project --no-annotate --frozen > .requirements.txt 2>/dev/null || \
|
|
33
|
+
uv pip freeze --exclude-editable > .requirements.txt && uv run app/agent_engine_app.py
|
|
23
34
|
{%- endif %}
|
|
24
35
|
|
|
25
36
|
{% if cookiecutter.deployment_target == 'cloud_run' -%}
|
|
@@ -34,8 +45,7 @@ ui:
|
|
|
34
45
|
setup-dev-env:
|
|
35
46
|
@if [ -z "$$PROJECT_ID" ]; then echo "Error: PROJECT_ID environment variable is not set"; exit 1; fi
|
|
36
47
|
(cd deployment/terraform/dev && terraform init && terraform apply --var-file vars/env.tfvars --var dev_project_id=$$PROJECT_ID --auto-approve)
|
|
37
|
-
|
|
38
|
-
{%- if cookiecutter.data_ingestion%}
|
|
48
|
+
{% if cookiecutter.data_ingestion %}
|
|
39
49
|
data-ingestion:
|
|
40
50
|
@if [ -z "$$PROJECT_ID" ]; then echo "Error: PROJECT_ID environment variable is not set"; exit 1; fi
|
|
41
51
|
$(MAKE) install
|
|
@@ -48,6 +58,7 @@ data-ingestion:
|
|
|
48
58
|
{%- elif cookiecutter.datastore_type == "vertex_ai_vector_search" %}
|
|
49
59
|
--vector-search-index="{{cookiecutter.project_name}}-vector-search" \
|
|
50
60
|
--vector-search-index-endpoint="{{cookiecutter.project_name}}-vector-search-endpoint" \
|
|
61
|
+
--vector-search-data-bucket-name="$$PROJECT_ID-{{cookiecutter.project_name}}-vs" \
|
|
51
62
|
{%- endif %}
|
|
52
63
|
--service-account="{{cookiecutter.project_name}}-rag@$$PROJECT_ID.iam.gserviceaccount.com" \
|
|
53
64
|
--pipeline-root="gs://$$PROJECT_ID-{{cookiecutter.project_name}}-rag" \
|
src/base_template/README.md
CHANGED
|
@@ -47,7 +47,7 @@ make install && make playground
|
|
|
47
47
|
| -------------------- | ------------------------------------------------------------------------------------------- |
|
|
48
48
|
| `make install` | Install all required dependencies using uv |
|
|
49
49
|
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
50
|
-
| `make playground` | Launch local development environment with backend and frontend |
|
|
50
|
+
| `make playground` | Launch local development environment with backend and frontend{%- if "adk" in cookiecutter.tags %} - leveraging `adk web` command. {%- endif %}|
|
|
51
51
|
| `make backend` | Start backend server only |
|
|
52
52
|
| `make ui` | Launch Streamlit frontend without local backend |
|
|
53
53
|
{%- elif cookiecutter.deployment_target == 'agent_engine' %}
|
|
@@ -56,6 +56,10 @@ make install && make playground
|
|
|
56
56
|
{%- endif %}
|
|
57
57
|
| `make test` | Run unit and integration tests |
|
|
58
58
|
| `make lint` | Run code quality checks (codespell, ruff, mypy) |
|
|
59
|
+
| `make setup-dev-env` | Set up development environment resources using Terraform |
|
|
60
|
+
{%- if cookiecutter.data_ingestion %}
|
|
61
|
+
| `make data-ingestion`| Run data ingestion pipeline in the Dev environment |
|
|
62
|
+
{%- endif %}
|
|
59
63
|
| `uv run jupyter lab` | Launch Jupyter notebook |
|
|
60
64
|
|
|
61
65
|
For full command options and usage, refer to the [Makefile](Makefile).
|
|
@@ -144,12 +148,14 @@ This template follows a "bring your own agent" approach - you focus on your busi
|
|
|
144
148
|
1. **Prototype:** Build your Generative AI Agent using the intro notebooks in `notebooks/` for guidance. Use Vertex AI Evaluation to assess performance.
|
|
145
149
|
2. **Integrate:** Import your agent into the app by editing `app/agent.py`.
|
|
146
150
|
3. **Test:** Explore your agent functionality using the Streamlit playground with `make playground`. The playground offers features like chat history, user feedback, and various input types, and automatically reloads your agent on code changes.
|
|
147
|
-
4. **Deploy:**
|
|
151
|
+
4. **Deploy:** Set up and initiate the CI/CD pipelines, customizing tests as necessary. Refer to the [deployment section](#deployment) for comprehensive instructions. For streamlined infrastructure deployment, simply run `agent-starter-pack setup-cicd`. Check out the [`agent-starter-pack setup-cicd` CLI command](https://github.com/GoogleCloudPlatform/agent-starter-pack/blob/main/docs/cli/setup_cicd.md). Currently only supporting Github.
|
|
148
152
|
5. **Monitor:** Track performance and gather insights using Cloud Logging, Tracing, and the Looker Studio dashboard to iterate on your application.
|
|
149
153
|
{% endif %}
|
|
150
154
|
|
|
151
155
|
## Deployment
|
|
152
156
|
|
|
157
|
+
> **Note:** For a streamlined one-command deployment of the entire CI/CD pipeline and infrastructure using Terraform, you can use the [`agent-starter-pack setup-cicd` CLI command](https://github.com/GoogleCloudPlatform/agent-starter-pack/blob/main/docs/cli/setup_cicd.md). Currently only supporting Github.
|
|
158
|
+
|
|
153
159
|
### Dev Environment
|
|
154
160
|
|
|
155
161
|
{%- if cookiecutter.deployment_target == 'agent_engine' %}
|
|
@@ -86,8 +86,18 @@ class CloudTraceLoggingSpanExporter(CloudTraceSpanExporter):
|
|
|
86
86
|
print(span_dict)
|
|
87
87
|
|
|
88
88
|
# Log the span data to Google Cloud Logging
|
|
89
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
90
|
+
self.logger.log_struct(
|
|
91
|
+
span_dict,
|
|
92
|
+
labels={
|
|
93
|
+
"type": "agent_telemetry",
|
|
94
|
+
"service_name": "{{cookiecutter.project_name}}",
|
|
95
|
+
},
|
|
96
|
+
severity="INFO",
|
|
97
|
+
)
|
|
98
|
+
{%- else %}
|
|
89
99
|
self.logger.log_struct(span_dict, severity="INFO")
|
|
90
|
-
|
|
100
|
+
{%- endif %}
|
|
91
101
|
# Export spans to Google Cloud Trace using the parent class method
|
|
92
102
|
return super().export(spans)
|
|
93
103
|
|
|
@@ -12,6 +12,29 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
16
|
+
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
17
|
+
import uuid
|
|
18
|
+
from typing import (
|
|
19
|
+
Literal,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from google.adk.events.event import Event
|
|
23
|
+
from google.genai.types import Content
|
|
24
|
+
from pydantic import (
|
|
25
|
+
BaseModel,
|
|
26
|
+
Field,
|
|
27
|
+
)
|
|
28
|
+
{%- else %}
|
|
29
|
+
from typing import (
|
|
30
|
+
Literal,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from pydantic import (
|
|
34
|
+
BaseModel,
|
|
35
|
+
)
|
|
36
|
+
{%- endif %}
|
|
37
|
+
{%- else %}
|
|
15
38
|
import json
|
|
16
39
|
import uuid
|
|
17
40
|
from typing import (
|
|
@@ -31,6 +54,24 @@ from pydantic import (
|
|
|
31
54
|
BaseModel,
|
|
32
55
|
Field,
|
|
33
56
|
)
|
|
57
|
+
{%- endif %}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
61
|
+
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Request(BaseModel):
|
|
65
|
+
"""Represents the input for a chat request with optional configuration."""
|
|
66
|
+
|
|
67
|
+
message: Content
|
|
68
|
+
events: list[Event]
|
|
69
|
+
user_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
70
|
+
session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
71
|
+
|
|
72
|
+
model_config = {"extra": "allow"}
|
|
73
|
+
{%- endif %}
|
|
74
|
+
{%- else %}
|
|
34
75
|
|
|
35
76
|
|
|
36
77
|
class InputChat(BaseModel):
|
|
@@ -42,7 +83,7 @@ class InputChat(BaseModel):
|
|
|
42
83
|
..., description="The chat messages representing the current conversation."
|
|
43
84
|
)
|
|
44
85
|
|
|
45
|
-
|
|
86
|
+
|
|
46
87
|
class Request(BaseModel):
|
|
47
88
|
"""Represents the input for a chat request with optional configuration.
|
|
48
89
|
|
|
@@ -54,16 +95,23 @@ class Request(BaseModel):
|
|
|
54
95
|
input: InputChat
|
|
55
96
|
config: RunnableConfig | None = None
|
|
56
97
|
|
|
57
|
-
{
|
|
98
|
+
{%- endif %}
|
|
99
|
+
|
|
100
|
+
|
|
58
101
|
class Feedback(BaseModel):
|
|
59
102
|
"""Represents feedback for a conversation."""
|
|
60
103
|
|
|
61
104
|
score: int | float
|
|
62
105
|
text: str | None = ""
|
|
106
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
107
|
+
invocation_id: str
|
|
108
|
+
{%- else %}
|
|
63
109
|
run_id: str
|
|
110
|
+
{%- endif %}
|
|
64
111
|
log_type: Literal["feedback"] = "feedback"
|
|
65
112
|
service_name: Literal["{{cookiecutter.project_name}}"] = "{{cookiecutter.project_name}}"
|
|
66
|
-
|
|
113
|
+
user_id: str = ""
|
|
114
|
+
{% if "adk" not in cookiecutter.tags %}
|
|
67
115
|
|
|
68
116
|
def ensure_valid_config(config: RunnableConfig | None) -> RunnableConfig:
|
|
69
117
|
"""Ensures a valid RunnableConfig by setting defaults for missing fields."""
|
|
@@ -99,7 +147,8 @@ def dumps(obj: Any) -> str:
|
|
|
99
147
|
JSON string representation of the object
|
|
100
148
|
"""
|
|
101
149
|
return json.dumps(obj, default=default_serialization)
|
|
102
|
-
{
|
|
150
|
+
{%- if cookiecutter.deployment_target == 'agent_engine' %}
|
|
151
|
+
|
|
103
152
|
|
|
104
153
|
def dumpd(obj: Any) -> Any:
|
|
105
154
|
"""
|
|
@@ -113,4 +162,5 @@ def dumpd(obj: Any) -> Any:
|
|
|
113
162
|
Dict/list representation of the object that can be JSON serialized
|
|
114
163
|
"""
|
|
115
164
|
return json.loads(dumps(obj))
|
|
165
|
+
{%- endif %}
|
|
116
166
|
{% endif %}
|
|
@@ -29,6 +29,8 @@ The application leverages [**Terraform**](http://terraform.io) to define and pro
|
|
|
29
29
|
|
|
30
30
|
## Setup
|
|
31
31
|
|
|
32
|
+
> **Note:** For a streamlined one-command deployment of the entire CI/CD pipeline and infrastructure using Terraform, you can use the [`agent-starter-pack setup-cicd` CLI command](https://github.com/GoogleCloudPlatform/agent-starter-pack/blob/main/docs/cli/setup_cicd.md). Currently only supporting Github.
|
|
33
|
+
|
|
32
34
|
**Prerequisites:**
|
|
33
35
|
|
|
34
36
|
1. A set of Google Cloud projects:
|
|
@@ -58,6 +60,7 @@ The application leverages [**Terraform**](http://terraform.io) to define and pro
|
|
|
58
60
|
|
|
59
61
|
| Variable | Description | Required |
|
|
60
62
|
| ---------------------- | --------------------------------------------------------------- | :------: |
|
|
63
|
+
| project_name | Project name used as a base for resource naming | Yes |
|
|
61
64
|
| prod_project_id | **Production** Google Cloud Project ID for resource deployment. | Yes |
|
|
62
65
|
| staging_project_id | **Staging** Google Cloud Project ID for resource deployment. | Yes |
|
|
63
66
|
| cicd_runner_project_id | Google Cloud Project ID where CI/CD pipelines will execute. | Yes |
|
|
@@ -65,7 +68,7 @@ The application leverages [**Terraform**](http://terraform.io) to define and pro
|
|
|
65
68
|
| host_connection_name | Name of the host connection you created in Cloud Build | Yes |
|
|
66
69
|
| repository_name | Name of the repository you added to Cloud Build | Yes |
|
|
67
70
|
|
|
68
|
-
Other optional variables include: telemetry and feedback
|
|
71
|
+
Other optional variables may include: telemetry and feedback log filters, service account roles, and for projects requiring data ingestion: pipeline cron schedule, pipeline roles, and datastore-specific configurations.
|
|
69
72
|
|
|
70
73
|
4. **Deploy Infrastructure with Terraform**
|
|
71
74
|
|