agent-starter-pack 0.11.2__py3-none-any.whl → 0.12.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.
Files changed (68) hide show
  1. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/METADATA +1 -1
  2. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/RECORD +41 -66
  3. agents/adk_base/app/__init__.py +17 -0
  4. agents/adk_base/notebooks/adk_app_testing.ipynb +4 -1
  5. agents/adk_base/tests/integration/test_agent.py +1 -1
  6. agents/agentic_rag/app/__init__.py +17 -0
  7. agents/agentic_rag/app/agent.py +2 -2
  8. agents/agentic_rag/notebooks/adk_app_testing.ipynb +4 -1
  9. agents/agentic_rag/tests/integration/test_agent.py +2 -2
  10. agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
  11. agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
  12. agents/live_api/tests/unit/test_server.py +6 -6
  13. llm.txt +15 -4
  14. src/base_template/Makefile +5 -5
  15. src/base_template/README.md +4 -4
  16. src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +2 -2
  17. src/base_template/pyproject.toml +2 -2
  18. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +1 -1
  19. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +1 -1
  20. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +2 -2
  21. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -1
  22. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -1
  23. src/cli/commands/create.py +25 -2
  24. src/cli/commands/enhance.py +94 -15
  25. src/cli/commands/list.py +1 -1
  26. src/cli/utils/remote_template.py +1 -1
  27. src/cli/utils/template.py +120 -41
  28. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +3 -3
  29. src/deployment_targets/agent_engine/{app → {{cookiecutter.agent_directory}}}/agent_engine_app.py +10 -10
  30. src/deployment_targets/cloud_run/Dockerfile +2 -2
  31. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +3 -3
  32. src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
  33. src/deployment_targets/cloud_run/tests/load_test/load_test.py +2 -2
  34. {agents/live_api/app → src/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}}/server.py +186 -7
  35. src/resources/docs/adk-cheatsheet.md +3 -3
  36. src/base_template/app/__init__.py +0 -3
  37. src/deployment_targets/cloud_run/app/server.py +0 -206
  38. src/frontends/adk_gemini_fullstack/frontend/components.json +0 -21
  39. src/frontends/adk_gemini_fullstack/frontend/eslint.config.js +0 -28
  40. src/frontends/adk_gemini_fullstack/frontend/index.html +0 -12
  41. src/frontends/adk_gemini_fullstack/frontend/package-lock.json +0 -6105
  42. src/frontends/adk_gemini_fullstack/frontend/package.json +0 -47
  43. src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +0 -564
  44. src/frontends/adk_gemini_fullstack/frontend/src/components/ActivityTimeline.tsx +0 -244
  45. src/frontends/adk_gemini_fullstack/frontend/src/components/ChatMessagesView.tsx +0 -420
  46. src/frontends/adk_gemini_fullstack/frontend/src/components/InputForm.tsx +0 -60
  47. src/frontends/adk_gemini_fullstack/frontend/src/components/WelcomeScreen.tsx +0 -56
  48. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/badge.tsx +0 -46
  49. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/button.tsx +0 -59
  50. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/card.tsx +0 -92
  51. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/input.tsx +0 -21
  52. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/scroll-area.tsx +0 -56
  53. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/select.tsx +0 -183
  54. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/tabs.tsx +0 -64
  55. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/textarea.tsx +0 -18
  56. src/frontends/adk_gemini_fullstack/frontend/src/global.css +0 -154
  57. src/frontends/adk_gemini_fullstack/frontend/src/main.tsx +0 -13
  58. src/frontends/adk_gemini_fullstack/frontend/src/utils.ts +0 -7
  59. src/frontends/adk_gemini_fullstack/frontend/src/vite-env.d.ts +0 -1
  60. src/frontends/adk_gemini_fullstack/frontend/tsconfig.json +0 -28
  61. src/frontends/adk_gemini_fullstack/frontend/tsconfig.node.json +0 -24
  62. src/frontends/adk_gemini_fullstack/frontend/vite.config.ts +0 -41
  63. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/WHEEL +0 -0
  64. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/entry_points.txt +0 -0
  65. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/licenses/LICENSE +0 -0
  66. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/gcs.py +0 -0
  67. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/tracing.py +0 -0
  68. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/typing.py +0 -0
@@ -20,7 +20,7 @@ WORKDIR /code
20
20
 
21
21
  COPY ./pyproject.toml ./README.md ./uv.lock* ./
22
22
 
23
- COPY ./app ./app
23
+ COPY ./{{cookiecutter.agent_directory}} ./{{cookiecutter.agent_directory}}
24
24
 
25
25
  RUN uv sync --frozen
26
26
 
@@ -29,4 +29,4 @@ ENV COMMIT_SHA=${COMMIT_SHA}
29
29
 
30
30
  EXPOSE 8080
31
31
 
32
- CMD ["uv", "run", "uvicorn", "app.server:app", "--host", "0.0.0.0", "--port", "8080"]
32
+ CMD ["uv", "run", "uvicorn", "{{cookiecutter.agent_directory}}.server:app", "--host", "0.0.0.0", "--port", "8080"]
@@ -54,7 +54,7 @@ def start_server() -> subprocess.Popen[str]:
54
54
  sys.executable,
55
55
  "-m",
56
56
  "uvicorn",
57
- "app.server:app",
57
+ "{{cookiecutter.agent_directory}}.server:app",
58
58
  "--host",
59
59
  "0.0.0.0",
60
60
  "--port",
@@ -129,7 +129,7 @@ def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
129
129
  user_id = "test_user_123"
130
130
  session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
131
131
 
132
- session_url = f"{BASE_URL}/apps/app/users/{user_id}/sessions"
132
+ session_url = f"{BASE_URL}/apps/{{cookiecutter.agent_directory}}/users/{user_id}/sessions"
133
133
  session_response = requests.post(
134
134
  session_url,
135
135
  headers=HEADERS,
@@ -142,7 +142,7 @@ def test_chat_stream(server_fixture: subprocess.Popen[str]) -> None:
142
142
 
143
143
  # Then send chat message
144
144
  data = {
145
- "app_name": "app",
145
+ "app_name": "{{cookiecutter.agent_directory}}",
146
146
  "user_id": user_id,
147
147
  "session_id": session_id,
148
148
  "new_message": {
@@ -11,7 +11,7 @@ Follow these steps to execute load tests on your local machine:
11
11
  Launch the FastAPI server in a separate terminal:
12
12
 
13
13
  ```bash
14
- uv run uvicorn app.server:app --host 0.0.0.0 --port 8000 --reload
14
+ uv run uvicorn {{cookiecutter.agent_directory}}.server:app --host 0.0.0.0 --port 8000 --reload
15
15
  ```
16
16
 
17
17
  **2. (In another tab) Create virtual environment with Locust**
@@ -45,7 +45,7 @@ class ChatStreamUser(HttpUser):
45
45
  user_id = f"user_{uuid.uuid4()}"
46
46
  session_data = {"state": {"preferred_language": "English", "visit_count": 1}}
47
47
 
48
- session_url = f"{self.client.base_url}/apps/app/users/{user_id}/sessions"
48
+ session_url = f"{self.client.base_url}/apps/{{cookiecutter.agent_directory}}/users/{user_id}/sessions"
49
49
  session_response = requests.post(
50
50
  session_url,
51
51
  headers=headers,
@@ -58,7 +58,7 @@ class ChatStreamUser(HttpUser):
58
58
 
59
59
  # Send chat message
60
60
  data = {
61
- "app_name": "app",
61
+ "app_name": "{{cookiecutter.agent_directory}}",
62
62
  "user_id": user_id,
63
63
  "session_id": session_id,
64
64
  "new_message": {
@@ -11,7 +11,7 @@
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
-
14
+ {% if cookiecutter.agent_name == "live_api" %}
15
15
  import asyncio
16
16
  import json
17
17
  import logging
@@ -27,7 +27,7 @@ from google.genai.types import LiveServerToolCall
27
27
  from pydantic import BaseModel
28
28
  from websockets.exceptions import ConnectionClosedError
29
29
 
30
- from app.agent import MODEL_ID, genai_client, live_connect_config, tool_functions
30
+ from .agent import MODEL_ID, genai_client, live_connect_config, tool_functions
31
31
 
32
32
  app = FastAPI()
33
33
  app.add_middleware(
@@ -197,16 +197,195 @@ class Feedback(BaseModel):
197
197
  run_id: str
198
198
  user_id: str | None
199
199
  log_type: Literal["feedback"] = "feedback"
200
+ {% elif "adk" in cookiecutter.tags %}
201
+ import os
202
+
203
+ import google.auth
204
+ from fastapi import FastAPI
205
+ from google.adk.cli.fast_api import get_fast_api_app
206
+ from google.cloud import logging as google_cloud_logging
207
+ from opentelemetry import trace
208
+ from opentelemetry.sdk.trace import TracerProvider, export
209
+ {%- if cookiecutter.session_type == "agent_engine" %}
210
+ from vertexai import agent_engines
211
+ {%- endif %}
212
+
213
+ from {{cookiecutter.agent_directory}}.utils.gcs import create_bucket_if_not_exists
214
+ from {{cookiecutter.agent_directory}}.utils.tracing import CloudTraceLoggingSpanExporter
215
+ from {{cookiecutter.agent_directory}}.utils.typing import Feedback
200
216
 
217
+ _, project_id = google.auth.default()
218
+ logging_client = google_cloud_logging.Client()
219
+ logger = logging_client.logger(__name__)
220
+ allow_origins = (
221
+ os.getenv("ALLOW_ORIGINS", "").split(",") if os.getenv("ALLOW_ORIGINS") else None
222
+ )
223
+
224
+ bucket_name = f"gs://{project_id}-{{cookiecutter.project_name}}-logs-data"
225
+ create_bucket_if_not_exists(
226
+ bucket_name=bucket_name, project=project_id, location="us-central1"
227
+ )
228
+
229
+ provider = TracerProvider()
230
+ processor = export.BatchSpanProcessor(CloudTraceLoggingSpanExporter())
231
+ provider.add_span_processor(processor)
232
+ trace.set_tracer_provider(provider)
233
+
234
+ AGENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
235
+
236
+ {%- if cookiecutter.session_type == "alloydb" %}
237
+ # AlloyDB session configuration
238
+ db_user = os.environ.get("DB_USER", "postgres")
239
+ db_name = os.environ.get("DB_NAME", "postgres")
240
+ db_pass = os.environ.get("DB_PASS")
241
+ db_host = os.environ.get("DB_HOST")
242
+
243
+ # Set session_service_uri if database credentials are available
244
+ session_service_uri = None
245
+ if db_host and db_pass:
246
+ session_service_uri = f"postgresql://{db_user}:{db_pass}@{db_host}:5432/{db_name}"
247
+ {%- elif cookiecutter.session_type == "agent_engine" %}
248
+ # Agent Engine session configuration
249
+ # Use environment variable for agent name, default to project name
250
+ agent_name = os.environ.get("AGENT_ENGINE_SESSION_NAME", "{{cookiecutter.project_name}}")
251
+
252
+ # Check if an agent with this name already exists
253
+ existing_agents = list(agent_engines.list(filter=f"display_name={agent_name}"))
254
+
255
+ if existing_agents:
256
+ # Use the existing agent
257
+ agent_engine = existing_agents[0]
258
+ else:
259
+ # Create a new agent if none exists
260
+ agent_engine = agent_engines.create(display_name=agent_name)
261
+
262
+ session_service_uri = f"agentengine://{agent_engine.resource_name}"
263
+ {%- else %}
264
+ # In-memory session configuration - no persistent storage
265
+ session_service_uri = None
266
+ {%- endif %}
267
+
268
+ app: FastAPI = get_fast_api_app(
269
+ agents_dir=AGENT_DIR,
270
+ web=True,
271
+ artifact_service_uri=bucket_name,
272
+ allow_origins=allow_origins,
273
+ session_service_uri=session_service_uri,
274
+ )
275
+ app.title = "{{cookiecutter.project_name}}"
276
+ app.description = "API for interacting with the Agent {{cookiecutter.project_name}}"
277
+ {% else %}
278
+ import logging
279
+ import os
280
+ from collections.abc import Generator
281
+
282
+ from fastapi import FastAPI
283
+ from fastapi.responses import RedirectResponse, StreamingResponse
284
+ from google.cloud import logging as google_cloud_logging
285
+ from langchain_core.runnables import RunnableConfig
286
+ from traceloop.sdk import Instruments, Traceloop
287
+
288
+ from {{cookiecutter.agent_directory}}.agent import agent
289
+ from {{cookiecutter.agent_directory}}.utils.tracing import CloudTraceLoggingSpanExporter
290
+ from {{cookiecutter.agent_directory}}.utils.typing import Feedback, InputChat, Request, dumps, ensure_valid_config
291
+
292
+ # Initialize FastAPI app and logging
293
+ app = FastAPI(
294
+ title="{{cookiecutter.project_name}}",
295
+ description="API for interacting with the Agent {{cookiecutter.project_name}}",
296
+ )
297
+ logging_client = google_cloud_logging.Client()
298
+ logger = logging_client.logger(__name__)
299
+
300
+ # Initialize Telemetry
301
+ try:
302
+ Traceloop.init(
303
+ app_name=app.title,
304
+ disable_batch=False,
305
+ exporter=CloudTraceLoggingSpanExporter(),
306
+ instruments={Instruments.LANGCHAIN, Instruments.CREW},
307
+ )
308
+ except Exception as e:
309
+ logging.error("Failed to initialize Telemetry: %s", str(e))
310
+
311
+
312
+ def set_tracing_properties(config: RunnableConfig) -> None:
313
+ """Sets tracing association properties for the current request.
314
+
315
+ Args:
316
+ config: Optional RunnableConfig containing request metadata
317
+ """
318
+ Traceloop.set_association_properties(
319
+ {
320
+ "log_type": "tracing",
321
+ "run_id": str(config.get("run_id", "None")),
322
+ "user_id": config["metadata"].pop("user_id", "None"),
323
+ "session_id": config["metadata"].pop("session_id", "None"),
324
+ "commit_sha": os.environ.get("COMMIT_SHA", "None"),
325
+ }
326
+ )
327
+
328
+
329
+ def stream_messages(
330
+ input: InputChat,
331
+ config: RunnableConfig | None = None,
332
+ ) -> Generator[str, None, None]:
333
+ """Stream events in response to an input chat.
334
+
335
+ Args:
336
+ input: The input chat messages
337
+ config: Optional configuration for the runnable
338
+
339
+ Yields:
340
+ JSON serialized event data
341
+ """
342
+ config = ensure_valid_config(config=config)
343
+ set_tracing_properties(config)
344
+ input_dict = input.model_dump()
345
+
346
+ for data in agent.stream(input_dict, config=config, stream_mode="messages"): # type: ignore[arg-type]
347
+ yield dumps(data) + "\n"
348
+
349
+
350
+ # Routes
351
+ @app.get("/", response_class=RedirectResponse)
352
+ def redirect_root_to_docs() -> RedirectResponse:
353
+ """Redirect the root URL to the API documentation."""
354
+ return RedirectResponse(url="/docs")
355
+
356
+
357
+ @app.post("/stream_messages")
358
+ def stream_chat_events(request: Request) -> StreamingResponse:
359
+ """Stream chat events in response to an input request.
360
+
361
+ Args:
362
+ request: The chat request containing input and config
363
+
364
+ Returns:
365
+ Streaming response of chat events
366
+ """
367
+ return StreamingResponse(
368
+ stream_messages(input=request.input, config=request.config),
369
+ media_type="text/event-stream",
370
+ )
371
+ {% endif %}
201
372
 
202
373
  @app.post("/feedback")
203
- async def collect_feedback(feedback_dict: Feedback) -> None:
204
- """Collect and log feedback."""
205
- feedback_data = feedback_dict.model_dump()
206
- logger.log_struct(feedback_data, severity="INFO")
374
+ def collect_feedback(feedback: Feedback) -> dict[str, str]:
375
+ """Collect and log feedback.
376
+
377
+ Args:
378
+ feedback: The feedback data to log
379
+
380
+ Returns:
381
+ Success message
382
+ """
383
+ logger.log_struct(feedback.model_dump(), severity="INFO")
384
+ return {"status": "success"}
207
385
 
208
386
 
387
+ # Main execution
209
388
  if __name__ == "__main__":
210
389
  import uvicorn
211
390
 
212
- uvicorn.run(app, host="0.0.0.0", port=8000, log_level="debug")
391
+ uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -1193,7 +1193,7 @@ import asyncio
1193
1193
 
1194
1194
  from google.adk.runners import Runner
1195
1195
  from google.adk.sessions import InMemorySessionService
1196
- from app.agent import root_agent
1196
+ from {{cookiecutter.agent_directory}}.agent import root_agent
1197
1197
  from google.genai import types as genai_types
1198
1198
 
1199
1199
 
@@ -1201,10 +1201,10 @@ async def main():
1201
1201
  """Runs the agent with a sample query."""
1202
1202
  session_service = InMemorySessionService()
1203
1203
  await session_service.create_session(
1204
- app_name="app", user_id="test_user", session_id="test_session"
1204
+ app_name="{{cookiecutter.agent_directory}}", user_id="test_user", session_id="test_session"
1205
1205
  )
1206
1206
  runner = Runner(
1207
- agent=root_agent, app_name="app", session_service=session_service
1207
+ agent=root_agent, app_name="{{cookiecutter.agent_directory}}", session_service=session_service
1208
1208
  )
1209
1209
  query = "I want a recipe for pancakes"
1210
1210
  async for event in runner.run_async(
@@ -1,3 +0,0 @@
1
- from app.agent import root_agent
2
-
3
- __all__ = ["root_agent"]
@@ -1,206 +0,0 @@
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 "adk" in cookiecutter.tags %}
15
- import os
16
-
17
- import google.auth
18
- from fastapi import FastAPI
19
- from google.adk.cli.fast_api import get_fast_api_app
20
- from google.cloud import logging as google_cloud_logging
21
- from opentelemetry import trace
22
- from opentelemetry.sdk.trace import TracerProvider, export
23
- {%- if cookiecutter.session_type == "agent_engine" %}
24
- from vertexai import agent_engines
25
- {%- endif %}
26
-
27
- from app.utils.gcs import create_bucket_if_not_exists
28
- from app.utils.tracing import CloudTraceLoggingSpanExporter
29
- from app.utils.typing import Feedback
30
-
31
- _, project_id = google.auth.default()
32
- logging_client = google_cloud_logging.Client()
33
- logger = logging_client.logger(__name__)
34
- allow_origins = (
35
- os.getenv("ALLOW_ORIGINS", "").split(",") if os.getenv("ALLOW_ORIGINS") else None
36
- )
37
-
38
- bucket_name = f"gs://{project_id}-{{cookiecutter.project_name}}-logs-data"
39
- create_bucket_if_not_exists(
40
- bucket_name=bucket_name, project=project_id, location="us-central1"
41
- )
42
-
43
- provider = TracerProvider()
44
- processor = export.BatchSpanProcessor(CloudTraceLoggingSpanExporter())
45
- provider.add_span_processor(processor)
46
- trace.set_tracer_provider(provider)
47
-
48
- AGENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
49
-
50
- {%- if cookiecutter.session_type == "alloydb" %}
51
- # AlloyDB session configuration
52
- db_user = os.environ.get("DB_USER", "postgres")
53
- db_name = os.environ.get("DB_NAME", "postgres")
54
- db_pass = os.environ.get("DB_PASS")
55
- db_host = os.environ.get("DB_HOST")
56
-
57
- # Set session_service_uri if database credentials are available
58
- session_service_uri = None
59
- if db_host and db_pass:
60
- session_service_uri = f"postgresql://{db_user}:{db_pass}@{db_host}:5432/{db_name}"
61
- {%- elif cookiecutter.session_type == "agent_engine" %}
62
- # Agent Engine session configuration
63
- # Use environment variable for agent name, default to project name
64
- agent_name = os.environ.get("AGENT_ENGINE_SESSION_NAME", "{{cookiecutter.project_name}}")
65
-
66
- # Check if an agent with this name already exists
67
- existing_agents = list(agent_engines.list(filter=f"display_name={agent_name}"))
68
-
69
- if existing_agents:
70
- # Use the existing agent
71
- agent_engine = existing_agents[0]
72
- else:
73
- # Create a new agent if none exists
74
- agent_engine = agent_engines.create(display_name=agent_name)
75
-
76
- session_service_uri = f"agentengine://{agent_engine.resource_name}"
77
- {%- else %}
78
- # In-memory session configuration - no persistent storage
79
- session_service_uri = None
80
- {%- endif %}
81
-
82
- app: FastAPI = get_fast_api_app(
83
- agents_dir=AGENT_DIR,
84
- web=True,
85
- artifact_service_uri=bucket_name,
86
- allow_origins=allow_origins,
87
- session_service_uri=session_service_uri,
88
- )
89
- app.title = "{{cookiecutter.project_name}}"
90
- app.description = "API for interacting with the Agent {{cookiecutter.project_name}}"
91
- {%- else %}
92
- import logging
93
- import os
94
- from collections.abc import Generator
95
-
96
- from fastapi import FastAPI
97
- from fastapi.responses import RedirectResponse, StreamingResponse
98
- from google.cloud import logging as google_cloud_logging
99
- from langchain_core.runnables import RunnableConfig
100
- from traceloop.sdk import Instruments, Traceloop
101
-
102
- from app.agent import agent
103
- from app.utils.tracing import CloudTraceLoggingSpanExporter
104
- from app.utils.typing import Feedback, InputChat, Request, dumps, ensure_valid_config
105
-
106
- # Initialize FastAPI app and logging
107
- app = FastAPI(
108
- title="{{cookiecutter.project_name}}",
109
- description="API for interacting with the Agent {{cookiecutter.project_name}}",
110
- )
111
- logging_client = google_cloud_logging.Client()
112
- logger = logging_client.logger(__name__)
113
-
114
- # Initialize Telemetry
115
- try:
116
- Traceloop.init(
117
- app_name=app.title,
118
- disable_batch=False,
119
- exporter=CloudTraceLoggingSpanExporter(),
120
- instruments={Instruments.LANGCHAIN, Instruments.CREW},
121
- )
122
- except Exception as e:
123
- logging.error("Failed to initialize Telemetry: %s", str(e))
124
-
125
-
126
- def set_tracing_properties(config: RunnableConfig) -> None:
127
- """Sets tracing association properties for the current request.
128
-
129
- Args:
130
- config: Optional RunnableConfig containing request metadata
131
- """
132
- Traceloop.set_association_properties(
133
- {
134
- "log_type": "tracing",
135
- "run_id": str(config.get("run_id", "None")),
136
- "user_id": config["metadata"].pop("user_id", "None"),
137
- "session_id": config["metadata"].pop("session_id", "None"),
138
- "commit_sha": os.environ.get("COMMIT_SHA", "None"),
139
- }
140
- )
141
-
142
-
143
- def stream_messages(
144
- input: InputChat,
145
- config: RunnableConfig | None = None,
146
- ) -> Generator[str, None, None]:
147
- """Stream events in response to an input chat.
148
-
149
- Args:
150
- input: The input chat messages
151
- config: Optional configuration for the runnable
152
-
153
- Yields:
154
- JSON serialized event data
155
- """
156
- config = ensure_valid_config(config=config)
157
- set_tracing_properties(config)
158
- input_dict = input.model_dump()
159
-
160
- for data in agent.stream(input_dict, config=config, stream_mode="messages"): # type: ignore[arg-type]
161
- yield dumps(data) + "\n"
162
-
163
-
164
- # Routes
165
- @app.get("/", response_class=RedirectResponse)
166
- def redirect_root_to_docs() -> RedirectResponse:
167
- """Redirect the root URL to the API documentation."""
168
- return RedirectResponse(url="/docs")
169
-
170
-
171
- @app.post("/stream_messages")
172
- def stream_chat_events(request: Request) -> StreamingResponse:
173
- """Stream chat events in response to an input request.
174
-
175
- Args:
176
- request: The chat request containing input and config
177
-
178
- Returns:
179
- Streaming response of chat events
180
- """
181
- return StreamingResponse(
182
- stream_messages(input=request.input, config=request.config),
183
- media_type="text/event-stream",
184
- )
185
- {%- endif %}
186
-
187
-
188
- @app.post("/feedback")
189
- def collect_feedback(feedback: Feedback) -> dict[str, str]:
190
- """Collect and log feedback.
191
-
192
- Args:
193
- feedback: The feedback data to log
194
-
195
- Returns:
196
- Success message
197
- """
198
- logger.log_struct(feedback.model_dump(), severity="INFO")
199
- return {"status": "success"}
200
-
201
-
202
- # Main execution
203
- if __name__ == "__main__":
204
- import uvicorn
205
-
206
- uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -1,21 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema.json",
3
- "style": "new-york",
4
- "rsc": false,
5
- "tsx": true,
6
- "tailwind": {
7
- "config": "",
8
- "css": "src/app.css",
9
- "baseColor": "neutral",
10
- "cssVariables": true,
11
- "prefix": ""
12
- },
13
- "aliases": {
14
- "components": "@/components",
15
- "utils": "@/lib/utils",
16
- "ui": "@/components/ui",
17
- "lib": "@/lib",
18
- "hooks": "@/hooks"
19
- },
20
- "iconLibrary": "lucide"
21
- }
@@ -1,28 +0,0 @@
1
- import js from '@eslint/js'
2
- import globals from 'globals'
3
- import reactHooks from 'eslint-plugin-react-hooks'
4
- import reactRefresh from 'eslint-plugin-react-refresh'
5
- import tseslint from 'typescript-eslint'
6
-
7
- export default tseslint.config(
8
- { ignores: ['dist'] },
9
- {
10
- extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
- files: ['**/*.{ts,tsx}'],
12
- languageOptions: {
13
- ecmaVersion: 2020,
14
- globals: globals.browser,
15
- },
16
- plugins: {
17
- 'react-hooks': reactHooks,
18
- 'react-refresh': reactRefresh,
19
- },
20
- rules: {
21
- ...reactHooks.configs.recommended.rules,
22
- 'react-refresh/only-export-components': [
23
- 'warn',
24
- { allowConstantExport: true },
25
- ],
26
- },
27
- },
28
- )
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>ADK Fullstack Agent</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="/src/main.tsx"></script>
11
- </body>
12
- </html>