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.
- agent_starter_pack-0.0.1b0.dist-info/METADATA +143 -0
- agent_starter_pack-0.0.1b0.dist-info/RECORD +162 -0
- agent_starter_pack-0.0.1b0.dist-info/WHEEL +4 -0
- agent_starter_pack-0.0.1b0.dist-info/entry_points.txt +2 -0
- agent_starter_pack-0.0.1b0.dist-info/licenses/LICENSE +201 -0
- agents/agentic_rag_vertexai_search/README.md +22 -0
- agents/agentic_rag_vertexai_search/app/agent.py +145 -0
- agents/agentic_rag_vertexai_search/app/retrievers.py +79 -0
- agents/agentic_rag_vertexai_search/app/templates.py +53 -0
- agents/agentic_rag_vertexai_search/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
- agents/agentic_rag_vertexai_search/template/.templateconfig.yaml +14 -0
- agents/agentic_rag_vertexai_search/tests/integration/test_agent.py +57 -0
- agents/crewai_coding_crew/README.md +34 -0
- agents/crewai_coding_crew/app/agent.py +86 -0
- agents/crewai_coding_crew/app/crew/config/agents.yaml +39 -0
- agents/crewai_coding_crew/app/crew/config/tasks.yaml +37 -0
- agents/crewai_coding_crew/app/crew/crew.py +71 -0
- agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +1571 -0
- agents/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
- agents/crewai_coding_crew/template/.templateconfig.yaml +12 -0
- agents/crewai_coding_crew/tests/integration/test_agent.py +47 -0
- agents/langgraph_base_react/README.md +9 -0
- agents/langgraph_base_react/app/agent.py +73 -0
- agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +1561 -0
- agents/langgraph_base_react/template/.templateconfig.yaml +13 -0
- agents/langgraph_base_react/tests/integration/test_agent.py +48 -0
- agents/multimodal_live_api/README.md +50 -0
- agents/multimodal_live_api/app/agent.py +86 -0
- agents/multimodal_live_api/app/server.py +193 -0
- agents/multimodal_live_api/app/templates.py +51 -0
- agents/multimodal_live_api/app/vector_store.py +55 -0
- agents/multimodal_live_api/template/.templateconfig.yaml +15 -0
- agents/multimodal_live_api/tests/integration/test_server_e2e.py +254 -0
- agents/multimodal_live_api/tests/load_test/load_test.py +40 -0
- agents/multimodal_live_api/tests/unit/test_server.py +143 -0
- src/base_template/.gitignore +197 -0
- src/base_template/Makefile +37 -0
- src/base_template/README.md +91 -0
- src/base_template/app/utils/tracing.py +143 -0
- src/base_template/app/utils/typing.py +115 -0
- src/base_template/deployment/README.md +123 -0
- src/base_template/deployment/cd/deploy-to-prod.yaml +98 -0
- src/base_template/deployment/cd/staging.yaml +215 -0
- src/base_template/deployment/ci/pr_checks.yaml +51 -0
- src/base_template/deployment/terraform/apis.tf +34 -0
- src/base_template/deployment/terraform/build_triggers.tf +122 -0
- src/base_template/deployment/terraform/dev/apis.tf +42 -0
- src/base_template/deployment/terraform/dev/iam.tf +90 -0
- src/base_template/deployment/terraform/dev/log_sinks.tf +66 -0
- src/base_template/deployment/terraform/dev/providers.tf +29 -0
- src/base_template/deployment/terraform/dev/storage.tf +76 -0
- src/base_template/deployment/terraform/dev/variables.tf +126 -0
- src/base_template/deployment/terraform/dev/vars/env.tfvars +21 -0
- src/base_template/deployment/terraform/iam.tf +130 -0
- src/base_template/deployment/terraform/locals.tf +50 -0
- src/base_template/deployment/terraform/log_sinks.tf +72 -0
- src/base_template/deployment/terraform/providers.tf +35 -0
- src/base_template/deployment/terraform/service_accounts.tf +42 -0
- src/base_template/deployment/terraform/storage.tf +100 -0
- src/base_template/deployment/terraform/variables.tf +202 -0
- src/base_template/deployment/terraform/vars/env.tfvars +43 -0
- src/base_template/pyproject.toml +113 -0
- src/base_template/tests/unit/test_utils/test_tracing_exporter.py +140 -0
- src/cli/commands/create.py +534 -0
- src/cli/commands/setup_cicd.py +730 -0
- src/cli/main.py +35 -0
- src/cli/utils/__init__.py +35 -0
- src/cli/utils/cicd.py +662 -0
- src/cli/utils/gcp.py +120 -0
- src/cli/utils/logging.py +51 -0
- src/cli/utils/template.py +644 -0
- src/data_ingestion/README.md +79 -0
- src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +175 -0
- src/data_ingestion/data_ingestion_pipeline/components/process_data.py +321 -0
- src/data_ingestion/data_ingestion_pipeline/pipeline.py +58 -0
- src/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +184 -0
- src/data_ingestion/pyproject.toml +17 -0
- src/data_ingestion/uv.lock +999 -0
- src/deployment_targets/agent_engine/app/agent_engine_app.py +238 -0
- src/deployment_targets/agent_engine/app/utils/gcs.py +42 -0
- src/deployment_targets/agent_engine/deployment_metadata.json +4 -0
- src/deployment_targets/agent_engine/notebooks/intro_reasoning_engine.ipynb +869 -0
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +120 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/report.html +264 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/results_exceptions.csv +1 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/results_failures.csv +1 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/results_stats.csv +3 -0
- src/deployment_targets/agent_engine/tests/load_test/.results/results_stats_history.csv +22 -0
- src/deployment_targets/agent_engine/tests/load_test/README.md +42 -0
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +100 -0
- src/deployment_targets/agent_engine/tests/unit/test_dummy.py +22 -0
- src/deployment_targets/cloud_run/Dockerfile +29 -0
- src/deployment_targets/cloud_run/app/server.py +128 -0
- src/deployment_targets/cloud_run/deployment/terraform/artifact_registry.tf +22 -0
- src/deployment_targets/cloud_run/deployment/terraform/dev/service_accounts.tf +20 -0
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +192 -0
- src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
- src/deployment_targets/cloud_run/tests/load_test/README.md +79 -0
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +85 -0
- src/deployment_targets/cloud_run/tests/unit/test_server.py +142 -0
- src/deployment_targets/cloud_run/uv.lock +6952 -0
- src/frontends/live_api_react/frontend/package-lock.json +19405 -0
- src/frontends/live_api_react/frontend/package.json +56 -0
- src/frontends/live_api_react/frontend/public/favicon.ico +0 -0
- src/frontends/live_api_react/frontend/public/index.html +62 -0
- src/frontends/live_api_react/frontend/public/robots.txt +3 -0
- src/frontends/live_api_react/frontend/src/App.scss +189 -0
- src/frontends/live_api_react/frontend/src/App.test.tsx +25 -0
- src/frontends/live_api_react/frontend/src/App.tsx +205 -0
- src/frontends/live_api_react/frontend/src/components/audio-pulse/AudioPulse.tsx +64 -0
- src/frontends/live_api_react/frontend/src/components/audio-pulse/audio-pulse.scss +68 -0
- src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +217 -0
- src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +201 -0
- src/frontends/live_api_react/frontend/src/components/logger/Logger.tsx +241 -0
- src/frontends/live_api_react/frontend/src/components/logger/logger.scss +133 -0
- src/frontends/live_api_react/frontend/src/components/logger/mock-logs.ts +151 -0
- src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +161 -0
- src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +285 -0
- src/frontends/live_api_react/frontend/src/contexts/LiveAPIContext.tsx +48 -0
- src/frontends/live_api_react/frontend/src/hooks/use-live-api.ts +115 -0
- src/frontends/live_api_react/frontend/src/hooks/use-media-stream-mux.ts +23 -0
- src/frontends/live_api_react/frontend/src/hooks/use-screen-capture.ts +72 -0
- src/frontends/live_api_react/frontend/src/hooks/use-webcam.ts +69 -0
- src/frontends/live_api_react/frontend/src/index.css +28 -0
- src/frontends/live_api_react/frontend/src/index.tsx +35 -0
- src/frontends/live_api_react/frontend/src/multimodal-live-types.ts +242 -0
- src/frontends/live_api_react/frontend/src/react-app-env.d.ts +17 -0
- src/frontends/live_api_react/frontend/src/reportWebVitals.ts +31 -0
- src/frontends/live_api_react/frontend/src/setupTests.ts +21 -0
- src/frontends/live_api_react/frontend/src/utils/audio-recorder.ts +111 -0
- src/frontends/live_api_react/frontend/src/utils/audio-streamer.ts +270 -0
- src/frontends/live_api_react/frontend/src/utils/audioworklet-registry.ts +43 -0
- src/frontends/live_api_react/frontend/src/utils/multimodal-live-client.ts +329 -0
- src/frontends/live_api_react/frontend/src/utils/store-logger.ts +64 -0
- src/frontends/live_api_react/frontend/src/utils/utils.ts +86 -0
- src/frontends/live_api_react/frontend/src/utils/worklets/audio-processing.ts +73 -0
- src/frontends/live_api_react/frontend/src/utils/worklets/vol-meter.ts +65 -0
- src/frontends/live_api_react/frontend/tsconfig.json +25 -0
- src/frontends/streamlit/frontend/side_bar.py +213 -0
- src/frontends/streamlit/frontend/streamlit_app.py +263 -0
- src/frontends/streamlit/frontend/style/app_markdown.py +37 -0
- src/frontends/streamlit/frontend/utils/chat_utils.py +67 -0
- src/frontends/streamlit/frontend/utils/local_chat_history.py +125 -0
- src/frontends/streamlit/frontend/utils/message_editing.py +59 -0
- src/frontends/streamlit/frontend/utils/multimodal_utils.py +217 -0
- src/frontends/streamlit/frontend/utils/stream_handler.py +282 -0
- src/frontends/streamlit/frontend/utils/title_summary.py +77 -0
- src/resources/containers/data_processing/Dockerfile +25 -0
- src/resources/locks/uv-agentic_rag_vertexai_search-agent_engine.lock +4684 -0
- src/resources/locks/uv-agentic_rag_vertexai_search-cloud_run.lock +5799 -0
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +5509 -0
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +6688 -0
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +4595 -0
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +5710 -0
- src/resources/locks/uv-multimodal_live_api-cloud_run.lock +5665 -0
- src/resources/setup_cicd/cicd_variables.tf +36 -0
- src/resources/setup_cicd/github.tf +85 -0
- src/resources/setup_cicd/providers.tf +39 -0
- src/utils/generate_locks.py +135 -0
- src/utils/lock_utils.py +82 -0
- 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
|
+

|
|
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
|
+

|
|
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
|
+
]
|