agent-starter-pack 0.0.1b0__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 (162) hide show
  1. agent_starter_pack-0.0.1b0.dist-info/METADATA +143 -0
  2. agent_starter_pack-0.0.1b0.dist-info/RECORD +162 -0
  3. agent_starter_pack-0.0.1b0.dist-info/WHEEL +4 -0
  4. agent_starter_pack-0.0.1b0.dist-info/entry_points.txt +2 -0
  5. agent_starter_pack-0.0.1b0.dist-info/licenses/LICENSE +201 -0
  6. agents/agentic_rag_vertexai_search/README.md +22 -0
  7. agents/agentic_rag_vertexai_search/app/agent.py +145 -0
  8. agents/agentic_rag_vertexai_search/app/retrievers.py +79 -0
  9. agents/agentic_rag_vertexai_search/app/templates.py +53 -0
  10. agents/agentic_rag_vertexai_search/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  11. agents/agentic_rag_vertexai_search/template/.templateconfig.yaml +14 -0
  12. agents/agentic_rag_vertexai_search/tests/integration/test_agent.py +57 -0
  13. agents/crewai_coding_crew/README.md +34 -0
  14. agents/crewai_coding_crew/app/agent.py +86 -0
  15. agents/crewai_coding_crew/app/crew/config/agents.yaml +39 -0
  16. agents/crewai_coding_crew/app/crew/config/tasks.yaml +37 -0
  17. agents/crewai_coding_crew/app/crew/crew.py +71 -0
  18. agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +1571 -0
  19. agents/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  20. agents/crewai_coding_crew/template/.templateconfig.yaml +12 -0
  21. agents/crewai_coding_crew/tests/integration/test_agent.py +47 -0
  22. agents/langgraph_base_react/README.md +9 -0
  23. agents/langgraph_base_react/app/agent.py +73 -0
  24. agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
  25. agents/langgraph_base_react/template/.templateconfig.yaml +13 -0
  26. agents/langgraph_base_react/tests/integration/test_agent.py +48 -0
  27. agents/multimodal_live_api/README.md +50 -0
  28. agents/multimodal_live_api/app/agent.py +86 -0
  29. agents/multimodal_live_api/app/server.py +193 -0
  30. agents/multimodal_live_api/app/templates.py +51 -0
  31. agents/multimodal_live_api/app/vector_store.py +55 -0
  32. agents/multimodal_live_api/template/.templateconfig.yaml +15 -0
  33. agents/multimodal_live_api/tests/integration/test_server_e2e.py +254 -0
  34. agents/multimodal_live_api/tests/load_test/load_test.py +40 -0
  35. agents/multimodal_live_api/tests/unit/test_server.py +143 -0
  36. src/base_template/.gitignore +197 -0
  37. src/base_template/Makefile +37 -0
  38. src/base_template/README.md +91 -0
  39. src/base_template/app/utils/tracing.py +143 -0
  40. src/base_template/app/utils/typing.py +115 -0
  41. src/base_template/deployment/README.md +123 -0
  42. src/base_template/deployment/cd/deploy-to-prod.yaml +98 -0
  43. src/base_template/deployment/cd/staging.yaml +215 -0
  44. src/base_template/deployment/ci/pr_checks.yaml +51 -0
  45. src/base_template/deployment/terraform/apis.tf +34 -0
  46. src/base_template/deployment/terraform/build_triggers.tf +122 -0
  47. src/base_template/deployment/terraform/dev/apis.tf +42 -0
  48. src/base_template/deployment/terraform/dev/iam.tf +90 -0
  49. src/base_template/deployment/terraform/dev/log_sinks.tf +66 -0
  50. src/base_template/deployment/terraform/dev/providers.tf +29 -0
  51. src/base_template/deployment/terraform/dev/storage.tf +76 -0
  52. src/base_template/deployment/terraform/dev/variables.tf +126 -0
  53. src/base_template/deployment/terraform/dev/vars/env.tfvars +21 -0
  54. src/base_template/deployment/terraform/iam.tf +130 -0
  55. src/base_template/deployment/terraform/locals.tf +50 -0
  56. src/base_template/deployment/terraform/log_sinks.tf +72 -0
  57. src/base_template/deployment/terraform/providers.tf +35 -0
  58. src/base_template/deployment/terraform/service_accounts.tf +42 -0
  59. src/base_template/deployment/terraform/storage.tf +100 -0
  60. src/base_template/deployment/terraform/variables.tf +202 -0
  61. src/base_template/deployment/terraform/vars/env.tfvars +43 -0
  62. src/base_template/pyproject.toml +113 -0
  63. src/base_template/tests/unit/test_utils/test_tracing_exporter.py +140 -0
  64. src/cli/commands/create.py +534 -0
  65. src/cli/commands/setup_cicd.py +730 -0
  66. src/cli/main.py +35 -0
  67. src/cli/utils/__init__.py +35 -0
  68. src/cli/utils/cicd.py +662 -0
  69. src/cli/utils/gcp.py +120 -0
  70. src/cli/utils/logging.py +51 -0
  71. src/cli/utils/template.py +644 -0
  72. src/data_ingestion/README.md +79 -0
  73. src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +175 -0
  74. src/data_ingestion/data_ingestion_pipeline/components/process_data.py +321 -0
  75. src/data_ingestion/data_ingestion_pipeline/pipeline.py +58 -0
  76. src/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +184 -0
  77. src/data_ingestion/pyproject.toml +17 -0
  78. src/data_ingestion/uv.lock +999 -0
  79. src/deployment_targets/agent_engine/app/agent_engine_app.py +238 -0
  80. src/deployment_targets/agent_engine/app/utils/gcs.py +42 -0
  81. src/deployment_targets/agent_engine/deployment_metadata.json +4 -0
  82. src/deployment_targets/agent_engine/notebooks/intro_reasoning_engine.ipynb +869 -0
  83. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +120 -0
  84. src/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
  85. src/deployment_targets/agent_engine/tests/load_test/.results/report.html +264 -0
  86. src/deployment_targets/agent_engine/tests/load_test/.results/results_exceptions.csv +1 -0
  87. src/deployment_targets/agent_engine/tests/load_test/.results/results_failures.csv +1 -0
  88. src/deployment_targets/agent_engine/tests/load_test/.results/results_stats.csv +3 -0
  89. src/deployment_targets/agent_engine/tests/load_test/.results/results_stats_history.csv +22 -0
  90. src/deployment_targets/agent_engine/tests/load_test/README.md +42 -0
  91. src/deployment_targets/agent_engine/tests/load_test/load_test.py +100 -0
  92. src/deployment_targets/agent_engine/tests/unit/test_dummy.py +22 -0
  93. src/deployment_targets/cloud_run/Dockerfile +29 -0
  94. src/deployment_targets/cloud_run/app/server.py +128 -0
  95. src/deployment_targets/cloud_run/deployment/terraform/artifact_registry.tf +22 -0
  96. src/deployment_targets/cloud_run/deployment/terraform/dev/service_accounts.tf +20 -0
  97. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +192 -0
  98. src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
  99. src/deployment_targets/cloud_run/tests/load_test/README.md +79 -0
  100. src/deployment_targets/cloud_run/tests/load_test/load_test.py +85 -0
  101. src/deployment_targets/cloud_run/tests/unit/test_server.py +142 -0
  102. src/deployment_targets/cloud_run/uv.lock +6952 -0
  103. src/frontends/live_api_react/frontend/package-lock.json +19405 -0
  104. src/frontends/live_api_react/frontend/package.json +56 -0
  105. src/frontends/live_api_react/frontend/public/favicon.ico +0 -0
  106. src/frontends/live_api_react/frontend/public/index.html +62 -0
  107. src/frontends/live_api_react/frontend/public/robots.txt +3 -0
  108. src/frontends/live_api_react/frontend/src/App.scss +189 -0
  109. src/frontends/live_api_react/frontend/src/App.test.tsx +25 -0
  110. src/frontends/live_api_react/frontend/src/App.tsx +205 -0
  111. src/frontends/live_api_react/frontend/src/components/audio-pulse/AudioPulse.tsx +64 -0
  112. src/frontends/live_api_react/frontend/src/components/audio-pulse/audio-pulse.scss +68 -0
  113. src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +217 -0
  114. src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +201 -0
  115. src/frontends/live_api_react/frontend/src/components/logger/Logger.tsx +241 -0
  116. src/frontends/live_api_react/frontend/src/components/logger/logger.scss +133 -0
  117. src/frontends/live_api_react/frontend/src/components/logger/mock-logs.ts +151 -0
  118. src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +161 -0
  119. src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +285 -0
  120. src/frontends/live_api_react/frontend/src/contexts/LiveAPIContext.tsx +48 -0
  121. src/frontends/live_api_react/frontend/src/hooks/use-live-api.ts +115 -0
  122. src/frontends/live_api_react/frontend/src/hooks/use-media-stream-mux.ts +23 -0
  123. src/frontends/live_api_react/frontend/src/hooks/use-screen-capture.ts +72 -0
  124. src/frontends/live_api_react/frontend/src/hooks/use-webcam.ts +69 -0
  125. src/frontends/live_api_react/frontend/src/index.css +28 -0
  126. src/frontends/live_api_react/frontend/src/index.tsx +35 -0
  127. src/frontends/live_api_react/frontend/src/multimodal-live-types.ts +242 -0
  128. src/frontends/live_api_react/frontend/src/react-app-env.d.ts +17 -0
  129. src/frontends/live_api_react/frontend/src/reportWebVitals.ts +31 -0
  130. src/frontends/live_api_react/frontend/src/setupTests.ts +21 -0
  131. src/frontends/live_api_react/frontend/src/utils/audio-recorder.ts +111 -0
  132. src/frontends/live_api_react/frontend/src/utils/audio-streamer.ts +270 -0
  133. src/frontends/live_api_react/frontend/src/utils/audioworklet-registry.ts +43 -0
  134. src/frontends/live_api_react/frontend/src/utils/multimodal-live-client.ts +329 -0
  135. src/frontends/live_api_react/frontend/src/utils/store-logger.ts +64 -0
  136. src/frontends/live_api_react/frontend/src/utils/utils.ts +86 -0
  137. src/frontends/live_api_react/frontend/src/utils/worklets/audio-processing.ts +73 -0
  138. src/frontends/live_api_react/frontend/src/utils/worklets/vol-meter.ts +65 -0
  139. src/frontends/live_api_react/frontend/tsconfig.json +25 -0
  140. src/frontends/streamlit/frontend/side_bar.py +213 -0
  141. src/frontends/streamlit/frontend/streamlit_app.py +263 -0
  142. src/frontends/streamlit/frontend/style/app_markdown.py +37 -0
  143. src/frontends/streamlit/frontend/utils/chat_utils.py +67 -0
  144. src/frontends/streamlit/frontend/utils/local_chat_history.py +125 -0
  145. src/frontends/streamlit/frontend/utils/message_editing.py +59 -0
  146. src/frontends/streamlit/frontend/utils/multimodal_utils.py +217 -0
  147. src/frontends/streamlit/frontend/utils/stream_handler.py +282 -0
  148. src/frontends/streamlit/frontend/utils/title_summary.py +77 -0
  149. src/resources/containers/data_processing/Dockerfile +25 -0
  150. src/resources/locks/uv-agentic_rag_vertexai_search-agent_engine.lock +4684 -0
  151. src/resources/locks/uv-agentic_rag_vertexai_search-cloud_run.lock +5799 -0
  152. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +5509 -0
  153. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +6688 -0
  154. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +4595 -0
  155. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +5710 -0
  156. src/resources/locks/uv-multimodal_live_api-cloud_run.lock +5665 -0
  157. src/resources/setup_cicd/cicd_variables.tf +36 -0
  158. src/resources/setup_cicd/github.tf +85 -0
  159. src/resources/setup_cicd/providers.tf +39 -0
  160. src/utils/generate_locks.py +135 -0
  161. src/utils/lock_utils.py +82 -0
  162. src/utils/watch_and_rebuild.py +190 -0
@@ -0,0 +1,13 @@
1
+ description: "A agent implementing a base ReAct agent using LangGraph"
2
+ settings:
3
+ requires_data_ingestion: false
4
+ deployment_targets: ["agent_engine", "cloud_run"]
5
+ extra_dependencies: [
6
+ "langchain-google-vertexai~=2.0.7",
7
+ "langchain~=0.3.14",
8
+ "langgraph~=0.2.63",
9
+ "langchain-google-vertexai~=2.0.7",
10
+ "langchain~=0.3.14",
11
+ "langchain-community~=0.3.17",
12
+ "langchain-openai~=0.3.5",
13
+ ]
@@ -0,0 +1,48 @@
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
+
15
+ # mypy: disable-error-code="union-attr"
16
+ from app.agent import agent
17
+
18
+
19
+ def test_agent_stream() -> None:
20
+ """
21
+ Integration test for the agent stream functionality.
22
+ Tests that the agent returns valid streaming responses.
23
+ """
24
+ input_dict = {
25
+ "messages": [
26
+ {"type": "human", "content": "Hi"},
27
+ {"type": "ai", "content": "Hi there!"},
28
+ {"type": "human", "content": "What's the weather in NY?"},
29
+ ]
30
+ }
31
+
32
+ events = [
33
+ message for message, _ in agent.stream(input_dict, stream_mode="messages")
34
+ ]
35
+
36
+ # Verify we get a reasonable number of messages
37
+ assert len(events) > 0, "Expected at least one message"
38
+
39
+ # First message should be an AI message
40
+ assert events[0].type == "AIMessageChunk"
41
+
42
+ # At least one message should have content
43
+ has_content = False
44
+ for event in events:
45
+ if hasattr(event, "content") and event.content:
46
+ has_content = True
47
+ break
48
+ assert has_content, "Expected at least one message with content"
@@ -0,0 +1,50 @@
1
+ # Multimodal Live Agent
2
+
3
+ This pattern showcases a real-time conversational RAG agent powered by Google Gemini. The agent handles audio, video, and text interactions while leveraging tool calling with a vector DB for grounded responses.
4
+
5
+ ![live_api_diagram](https://storage.googleapis.com/github-repo/generative-ai/sample-apps/e2e-gen-ai-app-starter-pack/live_api_diagram.png)
6
+
7
+ **Key components:**
8
+
9
+ - **Python Backend** (in `app/` folder): A production-ready server built with [FastAPI](https://fastapi.tiangolo.com/) and [google-genai](https://googleapis.github.io/python-genai/) that features:
10
+
11
+ - **Real-time bidirectional communication** via WebSockets between the frontend and Gemini model
12
+ - **Integrated tool calling** with vector database support for contextual document retrieval
13
+ - **Production-grade reliability** with retry logic and automatic reconnection capabilities
14
+ - **Deployment flexibility** supporting both AI Studio and Vertex AI endpoints
15
+ - **Feedback logging endpoint** for collecting user interactions
16
+
17
+ - **React Frontend** (in `frontend/` folder): Extends the [Multimodal live API Web Console](https://github.com/google-gemini/multimodal-live-api-web-console), with added features like **custom URLs** and **feedback collection**.
18
+
19
+ ![live api demo](https://storage.googleapis.com/github-repo/generative-ai/sample-apps/e2e-gen-ai-app-starter-pack/live_api_pattern_demo.gif)
20
+
21
+ ## Usage
22
+
23
+ 1. **Install Dependencies:**
24
+
25
+ ```bash
26
+ make install
27
+ ```
28
+
29
+ 2. **Start the Backend and Frontend:**
30
+
31
+ Start the backend:
32
+ ```bash
33
+ make backend
34
+ ```
35
+
36
+ In a different shell, start the frontend:
37
+ ```bash
38
+ make ui
39
+ ```
40
+
41
+ Once both the backend and frontend are running, click the play button in the frontend UI to establish a connection with the backend. You can now interact with the Multimodal Live Agent! You can try asking questions such as "Using the tool you have, define Governance in the context MLOPs" to allow the agent to use the [documentation](https://cloud.google.com/architecture/deploy-operate-generative-ai-applications) it was provided to.
42
+
43
+ ## Additional Resources for Multimodal Live API
44
+
45
+ Explore these resources to learn more about the Multimodal Live API and see examples of its usage:
46
+
47
+ - [Project Pastra](https://github.com/heiko-hotz/gemini-multimodal-live-dev-guide/tree/main): a comprehensive developer guide for the Gemini Multimodal Live API.
48
+ - [Google Cloud Multimodal Live API demos and samples](https://github.com/GoogleCloudPlatform/generative-ai/tree/main/gemini/multimodal-live-api): Collection of code samples and demo applications leveraging multimodal live API in Vertex AI
49
+ - [Gemini 2 Cookbook](https://github.com/google-gemini/cookbook/tree/main/gemini-2): Practical examples and tutorials for working with Gemini 2
50
+ - [Multimodal Live API Web Console](https://github.com/google-gemini/multimodal-live-api-web-console): Interactive React-based web interface for testing and experimenting with Gemini Multimodal Live API.
@@ -0,0 +1,86 @@
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
+
15
+ import os
16
+
17
+ import google
18
+ import vertexai
19
+ from google import genai
20
+ from google.genai.types import (
21
+ Content,
22
+ FunctionDeclaration,
23
+ LiveConnectConfig,
24
+ Tool,
25
+ )
26
+ from langchain_google_vertexai import VertexAIEmbeddings
27
+
28
+ from app.templates import FORMAT_DOCS, SYSTEM_INSTRUCTION
29
+ from app.vector_store import get_vector_store
30
+
31
+ # Constants
32
+ VERTEXAI = os.getenv("VERTEXAI", "true").lower() == "true"
33
+ LOCATION = "us-central1"
34
+ EMBEDDING_MODEL = "text-embedding-004"
35
+ MODEL_ID = "gemini-2.0-flash-001"
36
+ URLS = [
37
+ "https://cloud.google.com/architecture/deploy-operate-generative-ai-applications"
38
+ ]
39
+
40
+ # Initialize Google Cloud clients
41
+ credentials, project_id = google.auth.default()
42
+ vertexai.init(project=project_id, location=LOCATION)
43
+
44
+
45
+ if VERTEXAI:
46
+ genai_client = genai.Client(project=project_id, location=LOCATION, vertexai=True)
47
+ else:
48
+ # API key should be set using GOOGLE_API_KEY environment variable
49
+ genai_client = genai.Client(http_options={"api_version": "v1alpha"})
50
+
51
+ # Initialize vector store and retriever
52
+ embedding = VertexAIEmbeddings(model_name=EMBEDDING_MODEL)
53
+ vector_store = get_vector_store(embedding=embedding, urls=URLS)
54
+ retriever = vector_store.as_retriever()
55
+
56
+
57
+ def retrieve_docs(query: str) -> dict[str, str]:
58
+ """
59
+ Retrieves pre-formatted documents about MLOps (Machine Learning Operations),
60
+ Gen AI lifecycle, and production deployment best practices.
61
+
62
+ Args:
63
+ query: Search query string related to MLOps, Gen AI, or production deployment.
64
+
65
+ Returns:
66
+ A set of relevant, pre-formatted documents.
67
+ """
68
+ docs = retriever.invoke(query)
69
+ formatted_docs = FORMAT_DOCS.format(docs=docs)
70
+ return {"output": formatted_docs}
71
+
72
+
73
+ # Configure tools and live connection
74
+ retrieve_docs_tool = Tool(
75
+ function_declarations=[
76
+ FunctionDeclaration.from_callable(client=genai_client, callable=retrieve_docs)
77
+ ]
78
+ )
79
+
80
+ tool_functions = {"retrieve_docs": retrieve_docs}
81
+
82
+ live_connect_config = LiveConnectConfig(
83
+ response_modalities=["AUDIO"],
84
+ tools=[retrieve_docs_tool],
85
+ system_instruction=Content(parts=[{"text": SYSTEM_INSTRUCTION}]),
86
+ )
@@ -0,0 +1,193 @@
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
+
15
+ import asyncio
16
+ import json
17
+ import logging
18
+ from collections.abc import Callable
19
+ from typing import Any, Literal
20
+
21
+ import backoff
22
+ from fastapi import FastAPI, WebSocket
23
+ from fastapi.middleware.cors import CORSMiddleware
24
+ from google.cloud import logging as google_cloud_logging
25
+ from google.genai import types
26
+ from google.genai.types import LiveServerToolCall
27
+ from pydantic import BaseModel
28
+ from websockets.exceptions import ConnectionClosedError
29
+
30
+ from app.agent import MODEL_ID, genai_client, live_connect_config, tool_functions
31
+
32
+ app = FastAPI()
33
+ app.add_middleware(
34
+ CORSMiddleware,
35
+ allow_origins=["*"],
36
+ allow_methods=["*"],
37
+ allow_headers=["*"],
38
+ )
39
+ logging_client = google_cloud_logging.Client()
40
+ logger = logging_client.logger(__name__)
41
+ logging.basicConfig(level=logging.INFO)
42
+
43
+
44
+ class GeminiSession:
45
+ """Manages bidirectional communication between a client and the Gemini model."""
46
+
47
+ def __init__(
48
+ self, session: Any, websocket: WebSocket, tool_functions: dict[str, Callable]
49
+ ) -> None:
50
+ """Initialize the Gemini session.
51
+
52
+ Args:
53
+ session: The Gemini session
54
+ websocket: The client websocket connection
55
+ user_id: Unique identifier for this client
56
+ tool_functions: Dictionary of available tool functions
57
+ """
58
+ self.session = session
59
+ self.websocket = websocket
60
+ self.run_id = "n/a"
61
+ self.user_id = "n/a"
62
+ self.tool_functions = tool_functions
63
+
64
+ async def receive_from_client(self) -> None:
65
+ """Listen for and process messages from the client.
66
+
67
+ Continuously receives messages and forwards audio data to Gemini.
68
+ Handles connection errors gracefully.
69
+ """
70
+ while True:
71
+ try:
72
+ data = await self.websocket.receive_json()
73
+ if isinstance(data, dict) and (
74
+ "realtimeInput" in data or "clientContent" in data
75
+ ):
76
+ await self.session._ws.send(json.dumps(data))
77
+ elif "setup" in data:
78
+ self.run_id = data["setup"]["run_id"]
79
+ self.user_id = data["setup"]["user_id"]
80
+ logging.info(f"Setup data: {data['setup']}")
81
+ else:
82
+ logging.warning(f"Received unexpected input from client: {data}")
83
+ except ConnectionClosedError as e:
84
+ logging.warning(f"Client {self.user_id} closed connection: {e}")
85
+ break
86
+ except Exception as e:
87
+ logging.error(f"Error receiving from client {self.user_id}: {e!s}")
88
+ break
89
+
90
+ def _get_func(self, action_label: str) -> Callable | None:
91
+ """Get the tool function for a given action label."""
92
+ return None if action_label == "" else self.tool_functions.get(action_label)
93
+
94
+ async def _handle_tool_call(
95
+ self, session: Any, tool_call: LiveServerToolCall
96
+ ) -> None:
97
+ """Process tool calls from Gemini and send back responses.
98
+
99
+ Args:
100
+ session: The Gemini session
101
+ tool_call: Tool call request from Gemini
102
+ """
103
+ for fc in tool_call.function_calls:
104
+ logging.debug(f"Calling tool function: {fc.name} with args: {fc.args}")
105
+ response = self._get_func(fc.name)(**fc.args)
106
+ tool_response = types.LiveClientToolResponse(
107
+ function_responses=[
108
+ types.FunctionResponse(name=fc.name, id=fc.id, response=response)
109
+ ]
110
+ )
111
+ logging.debug(f"Tool response: {tool_response}")
112
+ await session.send(tool_response)
113
+
114
+ async def receive_from_gemini(self) -> None:
115
+ """Listen for and process messages from Gemini.
116
+
117
+ Continuously receives messages from Gemini, forwards them to the client,
118
+ and handles any tool calls. Handles connection errors gracefully.
119
+ """
120
+ while result := await self.session._ws.recv(decode=False):
121
+ await self.websocket.send_bytes(result)
122
+ message = types.LiveServerMessage.model_validate(json.loads(result))
123
+ if message.tool_call:
124
+ tool_call = LiveServerToolCall.model_validate(message.tool_call)
125
+ await self._handle_tool_call(self.session, tool_call)
126
+
127
+
128
+ def get_connect_and_run_callable(websocket: WebSocket) -> Callable:
129
+ """Create a callable that handles Gemini connection with retry logic.
130
+
131
+ Args:
132
+ websocket: The client websocket connection
133
+
134
+ Returns:
135
+ Callable: An async function that establishes and manages the Gemini connection
136
+ """
137
+
138
+ async def on_backoff(details: backoff._typing.Details) -> None:
139
+ await websocket.send_json(
140
+ {
141
+ "status": f"Model connection error, retrying in {details['wait']} seconds..."
142
+ }
143
+ )
144
+
145
+ @backoff.on_exception(
146
+ backoff.expo, ConnectionClosedError, max_tries=10, on_backoff=on_backoff
147
+ )
148
+ async def connect_and_run() -> None:
149
+ async with genai_client.aio.live.connect(
150
+ model=MODEL_ID, config=live_connect_config
151
+ ) as session:
152
+ await websocket.send_json({"status": "Backend is ready for conversation"})
153
+ gemini_session = GeminiSession(
154
+ session=session, websocket=websocket, tool_functions=tool_functions
155
+ )
156
+ logging.info("Starting bidirectional communication")
157
+ await asyncio.gather(
158
+ gemini_session.receive_from_client(),
159
+ gemini_session.receive_from_gemini(),
160
+ )
161
+
162
+ return connect_and_run
163
+
164
+
165
+ @app.websocket("/ws")
166
+ async def websocket_endpoint(websocket: WebSocket) -> None:
167
+ """Handle new websocket connections."""
168
+ await websocket.accept()
169
+ connect_and_run = get_connect_and_run_callable(websocket)
170
+ await connect_and_run()
171
+
172
+
173
+ class Feedback(BaseModel):
174
+ """Represents feedback for a conversation."""
175
+
176
+ score: int | float
177
+ text: str | None = ""
178
+ run_id: str
179
+ user_id: str | None
180
+ log_type: Literal["feedback"] = "feedback"
181
+
182
+
183
+ @app.post("/feedback")
184
+ async def collect_feedback(feedback_dict: Feedback) -> None:
185
+ """Collect and log feedback."""
186
+ feedback_data = feedback_dict.model_dump()
187
+ logger.log_struct(feedback_data, severity="INFO")
188
+
189
+
190
+ if __name__ == "__main__":
191
+ import uvicorn
192
+
193
+ uvicorn.run(app, host="0.0.0.0", port=8000, log_level="debug")
@@ -0,0 +1,51 @@
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
+
15
+ from langchain_core.prompts import PromptTemplate
16
+
17
+ FORMAT_DOCS = PromptTemplate.from_template(
18
+ """## Context provided:
19
+ {% for doc in docs%}
20
+ <Document {{ loop.index0 }}>
21
+ {{ doc.page_content | safe }}
22
+ </Document {{ loop.index0 }}>
23
+ {% endfor %}
24
+ """,
25
+ template_format="jinja2",
26
+ )
27
+
28
+ SYSTEM_INSTRUCTION = """You are "MLOps Expert," a specialized AI assistant designed to provide accurate and up-to-date information on Machine Learning Operations (MLOps), the lifecycle of Generative AI applications, and best practices for production deployment.
29
+
30
+ Your primary knowledge source is a powerful search tool that provides access to the most current MLOps documentation and resources. **For any question related to MLOps, the lifecycle of Gen AI Apps, or best practices for production deployment, you MUST use this tool as your first and foremost source of information.** Do not rely on your internal knowledge for these topics, as it may be outdated or incomplete.
31
+
32
+ **Here's how you should operate:**
33
+
34
+ 1. **Analyze the User's Question:** Determine if the question falls within the domain of MLOps, Gen AI lifecycle, or production deployment best practices.
35
+ 2. **Prioritize Tool Usage:** If the question is within the defined domain, use the provided search tool to find relevant information.
36
+ 3. **Synthesize and Respond:** Craft a clear, concise, and informative answer based *solely* on the information retrieved from the tool.
37
+ 4. **Cite Sources (Optional):** If possible and relevant, indicate which part of the answer came from the tool. For example, you can say, "According to the documentation I found..." or provide links if applicable.
38
+ 5. **Out-of-Scope Questions:** If the question is outside the scope of MLOps, Gen AI, or production deployment, politely state that the topic is beyond your current expertise. For example: "My expertise is in MLOps, and that question seems to be about a different area. I'm not equipped to answer it accurately."
39
+
40
+ **Your Persona:**
41
+
42
+ * You are an expert MLOps consultant, knowledgeable and up-to-date with the latest industry trends and best practices.
43
+ * You are helpful, professional, and eager to provide accurate information.
44
+ * You are concise and avoid unnecessary conversational filler. Get straight to the point.
45
+
46
+ **Example Interaction:**
47
+
48
+ **User:** "What are the best practices for monitoring a deployed ML model?"
49
+
50
+ **MLOps Expert:** (Uses the tool to search for "monitoring deployed ML model") "According to the MLOps documentation I have access to, the best practices for monitoring a deployed ML model include tracking data drift, model performance degradation, and system health metrics. Key metrics to monitor are..." (continues with information found in the tool).
51
+ """
@@ -0,0 +1,55 @@
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
+
15
+ import logging
16
+ import os
17
+
18
+ from langchain.schema import Document
19
+ from langchain.text_splitter import CharacterTextSplitter
20
+ from langchain_community.document_loaders import WebBaseLoader
21
+ from langchain_community.vectorstores import SKLearnVectorStore
22
+ from langchain_core.embeddings import Embeddings
23
+
24
+ PERSIST_PATH = ".persist_vector_store"
25
+
26
+
27
+ def load_and_split_documents(urls: list[str]) -> list[Document]:
28
+ """Load and split documents from a list of URLs."""
29
+ docs = [WebBaseLoader(url).load() for url in urls]
30
+ docs_list = [item for sublist in docs for item in sublist]
31
+ logging.info(f"# of documents loaded (pre-chunking) = {len(docs_list)}")
32
+
33
+ text_splitter = CharacterTextSplitter(chunk_size=2000, chunk_overlap=50)
34
+ doc_splits = text_splitter.split_documents(docs_list)
35
+ logging.info(f"# of documents after split = {len(doc_splits)}")
36
+
37
+ return doc_splits
38
+
39
+
40
+ def get_vector_store(
41
+ embedding: Embeddings, urls: list[str], persist_path: str = PERSIST_PATH
42
+ ) -> SKLearnVectorStore:
43
+ """Get or create a vector store."""
44
+
45
+ if os.path.exists(persist_path):
46
+ vector_store = SKLearnVectorStore(
47
+ embedding=embedding, persist_path=persist_path
48
+ )
49
+ else:
50
+ doc_splits = load_and_split_documents(urls=urls)
51
+ vector_store = SKLearnVectorStore.from_documents(
52
+ documents=doc_splits, embedding=embedding, persist_path=persist_path
53
+ )
54
+ vector_store.persist()
55
+ return vector_store
@@ -0,0 +1,15 @@
1
+ description: "A real-time multimodal RAG agent powered by Gemini, supporting audio/video/text chat with vector DB-backed responses"
2
+ settings:
3
+ requires_data_ingestion: false
4
+ frontend_type: "live_api_react"
5
+ deployment_targets: ["cloud_run"]
6
+ extra_dependencies: [
7
+ "backoff~=2.2.1",
8
+ "beautifulsoup4~=4.12.3",
9
+ "google-genai~=1.2.0",
10
+ "jinja2~=3.1.4",
11
+ "langchain~=0.3.13",
12
+ "langchain-community~=0.3.13",
13
+ "langchain-google-vertexai~=2.0.9",
14
+ "scikit-learn>=1.0.0,<2.0.0",
15
+ ]