agent-starter-pack 0.5.2__py3-none-any.whl → 0.6.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 (75) hide show
  1. {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/METADATA +2 -2
  2. {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/RECORD +66 -40
  3. agents/adk_base/notebooks/adk_app_testing.ipynb +1 -1
  4. agents/adk_base/template/.templateconfig.yaml +1 -1
  5. agents/adk_gemini_fullstack/README.md +148 -0
  6. agents/adk_gemini_fullstack/app/agent.py +349 -0
  7. agents/adk_gemini_fullstack/app/config.py +11 -0
  8. agents/adk_gemini_fullstack/notebooks/adk_app_testing.ipynb +353 -0
  9. agents/adk_gemini_fullstack/notebooks/evaluating_adk_agent.ipynb +1528 -0
  10. agents/adk_gemini_fullstack/template/.templateconfig.yaml +37 -0
  11. agents/adk_gemini_fullstack/tests/integration/test_agent.py +58 -0
  12. agents/agentic_rag/notebooks/adk_app_testing.ipynb +1 -1
  13. agents/agentic_rag/template/.templateconfig.yaml +1 -1
  14. agents/crewai_coding_crew/template/.templateconfig.yaml +1 -1
  15. agents/langgraph_base_react/template/.templateconfig.yaml +1 -1
  16. src/base_template/Makefile +21 -2
  17. src/base_template/README.md +8 -3
  18. src/base_template/pyproject.toml +1 -4
  19. src/cli/commands/create.py +17 -10
  20. src/cli/utils/template.py +13 -10
  21. src/data_ingestion/data_ingestion_pipeline/components/process_data.py +1 -1
  22. src/deployment_targets/agent_engine/app/agent_engine_app.py +17 -5
  23. src/deployment_targets/cloud_run/app/server.py +17 -2
  24. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +1 -1
  25. src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +321 -0
  26. src/frontends/adk_gemini_fullstack/frontend/components.json +21 -0
  27. src/frontends/adk_gemini_fullstack/frontend/eslint.config.js +28 -0
  28. src/frontends/adk_gemini_fullstack/frontend/index.html +12 -0
  29. src/frontends/adk_gemini_fullstack/frontend/package-lock.json +5829 -0
  30. src/frontends/adk_gemini_fullstack/frontend/package.json +46 -0
  31. src/frontends/adk_gemini_fullstack/frontend/public/vite.svg +1 -0
  32. src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +565 -0
  33. src/frontends/adk_gemini_fullstack/frontend/src/components/ActivityTimeline.tsx +244 -0
  34. src/frontends/adk_gemini_fullstack/frontend/src/components/ChatMessagesView.tsx +419 -0
  35. src/frontends/adk_gemini_fullstack/frontend/src/components/InputForm.tsx +60 -0
  36. src/frontends/adk_gemini_fullstack/frontend/src/components/WelcomeScreen.tsx +56 -0
  37. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/badge.tsx +46 -0
  38. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/button.tsx +59 -0
  39. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/card.tsx +92 -0
  40. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/input.tsx +21 -0
  41. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/scroll-area.tsx +56 -0
  42. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/select.tsx +183 -0
  43. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/tabs.tsx +64 -0
  44. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/textarea.tsx +18 -0
  45. src/frontends/adk_gemini_fullstack/frontend/src/global.css +154 -0
  46. src/frontends/adk_gemini_fullstack/frontend/src/main.tsx +13 -0
  47. src/frontends/adk_gemini_fullstack/frontend/src/utils.ts +7 -0
  48. src/frontends/adk_gemini_fullstack/frontend/src/vite-env.d.ts +1 -0
  49. src/frontends/adk_gemini_fullstack/frontend/tsconfig.json +28 -0
  50. src/frontends/adk_gemini_fullstack/frontend/tsconfig.node.json +24 -0
  51. src/frontends/adk_gemini_fullstack/frontend/vite.config.ts +26 -0
  52. src/resources/locks/uv-adk_base-agent_engine.lock +303 -251
  53. src/resources/locks/uv-adk_base-cloud_run.lock +367 -306
  54. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +3217 -0
  55. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +3513 -0
  56. src/resources/locks/uv-agentic_rag-agent_engine.lock +621 -565
  57. src/resources/locks/uv-agentic_rag-cloud_run.lock +854 -782
  58. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +860 -723
  59. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +1139 -960
  60. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +718 -587
  61. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +977 -815
  62. src/resources/locks/uv-live_api-cloud_run.lock +784 -709
  63. src/frontends/streamlit_adk/frontend/side_bar.py +0 -214
  64. src/frontends/streamlit_adk/frontend/streamlit_app.py +0 -314
  65. src/frontends/streamlit_adk/frontend/style/app_markdown.py +0 -37
  66. src/frontends/streamlit_adk/frontend/utils/chat_utils.py +0 -84
  67. src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +0 -110
  68. src/frontends/streamlit_adk/frontend/utils/message_editing.py +0 -61
  69. src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +0 -223
  70. src/frontends/streamlit_adk/frontend/utils/stream_handler.py +0 -311
  71. src/frontends/streamlit_adk/frontend/utils/title_summary.py +0 -129
  72. {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/WHEEL +0 -0
  73. {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/entry_points.txt +0 -0
  74. {agent_starter_pack-0.5.2.dist-info → agent_starter_pack-0.6.0.dist-info}/licenses/LICENSE +0 -0
  75. /src/{deployment_targets/agent_engine → base_template}/app/utils/gcs.py +0 -0
@@ -1,214 +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
-
15
- # ruff: noqa: RUF015
16
- import json
17
- import os
18
- import uuid
19
- from typing import Any
20
-
21
- from frontend.utils.chat_utils import save_chat
22
- from frontend.utils.multimodal_utils import (
23
- HELP_GCS_CHECKBOX,
24
- HELP_MESSAGE_MULTIMODALITY,
25
- upload_files_to_gcs,
26
- )
27
-
28
- EMPTY_CHAT_NAME = "Empty chat"
29
- NUM_CHAT_IN_RECENT = 3
30
- DEFAULT_BASE_URL = "http://localhost:8000/"
31
-
32
- DEFAULT_REMOTE_AGENT_ENGINE_ID = "N/A"
33
- if os.path.exists("deployment_metadata.json"):
34
- with open("deployment_metadata.json") as f:
35
- DEFAULT_REMOTE_AGENT_ENGINE_ID = json.load(f)["remote_agent_engine_id"]
36
- DEFAULT_AGENT_CALLABLE_PATH = "app.agent_engine_app.AgentEngineApp"
37
-
38
-
39
- class SideBar:
40
- """Manages the sidebar components of the Streamlit application."""
41
-
42
- def __init__(self, st: Any) -> None:
43
- """
44
- Initialize the SideBar.
45
-
46
- Args:
47
- st (Any): The Streamlit object for rendering UI components.
48
- """
49
- self.st = st
50
-
51
- def init_side_bar(self) -> None:
52
- """Initialize and render the sidebar components."""
53
- with self.st.sidebar:
54
- default_agent_type = (
55
- "Remote URL" if os.path.exists("Dockerfile") else "Local Agent"
56
- )
57
- use_agent_path = self.st.selectbox(
58
- "Select Agent Type",
59
- ["Local Agent", "Remote Agent Engine ID", "Remote URL"],
60
- index=["Local Agent", "Remote Agent Engine ID", "Remote URL"].index(
61
- default_agent_type
62
- ),
63
- help="'Local Agent' uses a local implementation, 'Remote Agent Engine ID' connects to a deployed Vertex AI agent, and 'Remote URL' connects to a custom endpoint.",
64
- )
65
-
66
- if use_agent_path == "Local Agent":
67
- self.agent_callable_path = self.st.text_input(
68
- label="Agent Callable Path",
69
- value=os.environ.get(
70
- "AGENT_CALLABLE_PATH", DEFAULT_AGENT_CALLABLE_PATH
71
- ),
72
- )
73
- self.remote_agent_engine_id = None
74
- self.url_input_field = None
75
- self.should_authenticate_request = False
76
- elif use_agent_path == "Remote Agent Engine ID":
77
- self.remote_agent_engine_id = self.st.text_input(
78
- label="Remote Agent Engine ID",
79
- value=os.environ.get(
80
- "REMOTE_AGENT_ENGINE_ID", DEFAULT_REMOTE_AGENT_ENGINE_ID
81
- ),
82
- )
83
- self.agent_callable_path = None
84
- self.url_input_field = None
85
- self.should_authenticate_request = False
86
- else:
87
- self.url_input_field = self.st.text_input(
88
- label="Service URL",
89
- value=os.environ.get("SERVICE_URL", DEFAULT_BASE_URL),
90
- )
91
- self.should_authenticate_request = self.st.checkbox(
92
- label="Authenticate request",
93
- value=False,
94
- help="If checked, any request to the server will contain an"
95
- "Identity token to allow authentication. "
96
- "See the Cloud Run documentation to know more about authentication:"
97
- "https://cloud.google.com/run/docs/authenticating/service-to-service",
98
- )
99
- self.agent_callable_path = None
100
- self.remote_agent_engine_id = None
101
-
102
- col1, col2, col3 = self.st.columns(3)
103
- with col1:
104
- if self.st.button("+ New chat"):
105
- if (
106
- len(
107
- self.st.session_state.user_chats[
108
- self.st.session_state["session_id"]
109
- ]["messages"]
110
- )
111
- > 0
112
- ):
113
- self.st.session_state.invocation_id = None
114
-
115
- self.st.session_state["session_id"] = str(uuid.uuid4())
116
- self.st.session_state.session_db.get_session(
117
- session_id=self.st.session_state["session_id"],
118
- )
119
- self.st.session_state.user_chats[
120
- self.st.session_state["session_id"]
121
- ] = {
122
- "title": EMPTY_CHAT_NAME,
123
- "messages": [],
124
- }
125
-
126
- with col2:
127
- if self.st.button("Delete chat"):
128
- self.st.session_state.invocation_id = None
129
- self.st.session_state.session_db.clear()
130
- self.st.session_state.user_chats.pop(
131
- self.st.session_state["session_id"]
132
- )
133
- if len(self.st.session_state.user_chats) > 0:
134
- chat_id = list(self.st.session_state.user_chats.keys())[0]
135
- self.st.session_state["session_id"] = chat_id
136
- self.st.session_state.session_db.get_session(
137
- session_id=self.st.session_state["session_id"],
138
- )
139
- else:
140
- self.st.session_state["session_id"] = str(uuid.uuid4())
141
- self.st.session_state.user_chats[
142
- self.st.session_state["session_id"]
143
- ] = {
144
- "title": EMPTY_CHAT_NAME,
145
- "messages": [],
146
- }
147
- with col3:
148
- if self.st.button("Save chat"):
149
- save_chat(self.st)
150
-
151
- self.st.subheader("Recent") # Style the heading
152
-
153
- all_chats = list(reversed(self.st.session_state.user_chats.items()))
154
- for chat_id, chat in all_chats[:NUM_CHAT_IN_RECENT]:
155
- if self.st.button(chat["title"], key=chat_id):
156
- self.st.session_state.invocation_id = None
157
- self.st.session_state["session_id"] = chat_id
158
- self.st.session_state.session_db.get_session(
159
- session_id=self.st.session_state["session_id"],
160
- )
161
-
162
- with self.st.expander("Other chats"):
163
- for chat_id, chat in all_chats[NUM_CHAT_IN_RECENT:]:
164
- if self.st.button(chat["title"], key=chat_id):
165
- self.st.session_state.invocation_id = None
166
- self.st.session_state["session_id"] = chat_id
167
- self.st.session_state.session_db.get_session(
168
- session_id=self.st.session_state["session_id"],
169
- )
170
-
171
- self.st.divider()
172
- self.st.header("Upload files from local")
173
- bucket_name = self.st.text_input(
174
- label="GCS Bucket for upload",
175
- value=os.environ.get("BUCKET_NAME", "gs://your-bucket-name"),
176
- )
177
- if "checkbox_state" not in self.st.session_state:
178
- self.st.session_state.checkbox_state = True
179
-
180
- self.st.session_state.checkbox_state = self.st.checkbox(
181
- "Upload to GCS first (suggested)", value=False, help=HELP_GCS_CHECKBOX
182
- )
183
-
184
- self.uploaded_files = self.st.file_uploader(
185
- label="Send files from local",
186
- accept_multiple_files=True,
187
- key=f"uploader_images_{self.st.session_state.uploader_key}",
188
- type=[
189
- "png",
190
- "jpg",
191
- "jpeg",
192
- "txt",
193
- "docx",
194
- "pdf",
195
- "rtf",
196
- "csv",
197
- "tsv",
198
- "xlsx",
199
- ],
200
- )
201
- if self.uploaded_files and self.st.session_state.checkbox_state:
202
- upload_files_to_gcs(self.st, bucket_name, self.uploaded_files)
203
-
204
- self.st.divider()
205
-
206
- self.st.header("Upload files from GCS")
207
- self.gcs_uris = self.st.text_area(
208
- "GCS uris (comma-separated)",
209
- value=self.st.session_state["gcs_uris_to_be_sent"],
210
- key=f"upload_text_area_{self.st.session_state.uploader_key}",
211
- help=HELP_MESSAGE_MULTIMODALITY,
212
- )
213
-
214
- self.st.caption(f"Note: {HELP_MESSAGE_MULTIMODALITY}")
@@ -1,314 +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
-
15
- # mypy: disable-error-code="arg-type"
16
- import json
17
- import uuid
18
- from collections.abc import Sequence
19
- from functools import partial
20
- from typing import Any
21
-
22
- import streamlit as st
23
- from google.adk.events.event import Event
24
- from google.genai import types
25
- from streamlit_feedback import streamlit_feedback
26
-
27
- from frontend.side_bar import SideBar
28
- from frontend.style.app_markdown import MARKDOWN_STR
29
- from frontend.utils.local_chat_history import LocalChatMessageHistory
30
- from frontend.utils.message_editing import MessageEditing
31
- from frontend.utils.multimodal_utils import format_content, get_parts_from_files
32
- from frontend.utils.stream_handler import Client, StreamHandler, get_chain_response
33
-
34
- USER = "my_user"
35
- EMPTY_CHAT_NAME = "Empty chat"
36
-
37
-
38
- def setup_page() -> None:
39
- """Configure the Streamlit page settings."""
40
- st.set_page_config(
41
- page_title="Playground",
42
- layout="wide",
43
- initial_sidebar_state="auto",
44
- menu_items=None,
45
- )
46
- st.title("Playground")
47
- st.markdown(MARKDOWN_STR, unsafe_allow_html=True)
48
-
49
-
50
- def initialize_session_state() -> None:
51
- """Initialize the session state with default values."""
52
- if "user_chats" not in st.session_state:
53
- st.session_state["session_id"] = str(uuid.uuid4())
54
- st.session_state.uploader_key = 0
55
- st.session_state.invocation_id = None
56
- st.session_state.user_id = USER
57
- st.session_state["gcs_uris_to_be_sent"] = ""
58
- st.session_state.modified_prompt = None
59
- st.session_state.session_db = LocalChatMessageHistory(
60
- session_id=st.session_state["session_id"],
61
- user_id=st.session_state["user_id"],
62
- )
63
- st.session_state.user_chats = (
64
- st.session_state.session_db.get_all_conversations()
65
- )
66
- st.session_state.user_chats[st.session_state["session_id"]] = {
67
- "title": EMPTY_CHAT_NAME,
68
- "messages": [],
69
- }
70
-
71
-
72
- def display_messages() -> None:
73
- """Display all messages in the current chat session."""
74
- messages = st.session_state.user_chats[st.session_state["session_id"]]["messages"]
75
- tool_calls_map = {} # Map tool_call_id to tool call input
76
- for i, message in enumerate(messages):
77
- # Convert message to Event if it's not already
78
- event = message if isinstance(message, Event) else Event.model_validate(message)
79
-
80
- # Check if this is a model message with function calls
81
- if hasattr(event.content, "parts") and event.content.parts:
82
- for part in event.content.parts:
83
- if hasattr(part, "function_call") and part.function_call:
84
- # Store function call info for later matching with responses
85
- tool_calls_map[part.function_call.id] = {
86
- "id": part.function_call.id,
87
- "name": part.function_call.name,
88
- "args": part.function_call.args,
89
- }
90
-
91
- # Check if this is a message with function responses
92
- function_responses = []
93
- if hasattr(event.content, "parts") and event.content.parts:
94
- for part in event.content.parts:
95
- if hasattr(part, "function_response") and part.function_response:
96
- function_responses.append(part.function_response)
97
-
98
- # Display function responses if any
99
- for function_response in function_responses:
100
- tool_call_id = function_response.id
101
- if tool_call_id in tool_calls_map:
102
- # Display the tool output and remove from map
103
- tool_call = tool_calls_map.pop(tool_call_id, None)
104
- if tool_call:
105
- display_tool_output(
106
- tool_call,
107
- {
108
- "type": "tool",
109
- "content": function_response.response,
110
- "tool_call_id": tool_call_id,
111
- },
112
- )
113
-
114
- # Display regular chat messages (model or user)
115
- if hasattr(event.content, "role") and event.content.role in ["model", "user"]:
116
- # Only display if there's text content (skip pure function call messages)
117
- has_text_content = False
118
- if hasattr(event.content, "parts"):
119
- for part in event.content.parts:
120
- if hasattr(part, "text") and part.text:
121
- has_text_content = True
122
- break
123
-
124
- if has_text_content:
125
- display_chat_message(message, i)
126
-
127
-
128
- def display_chat_message(message: dict[str, Any], index: int) -> None:
129
- """Display a single chat message with edit, refresh, and delete options."""
130
- role = "assistant" if message["content"]["role"] == "model" else "user"
131
- chat_message = st.chat_message(role)
132
- with chat_message:
133
- st.markdown(format_content(message["content"]["parts"]), unsafe_allow_html=True)
134
- col1, col2, col3 = st.columns([2, 2, 94])
135
- display_message_buttons(message, index, col1, col2, col3)
136
-
137
-
138
- def display_message_buttons(
139
- message: dict[str, Any], index: int, col1: Any, col2: Any, col3: Any
140
- ) -> None:
141
- """Display edit, refresh, and delete buttons for a chat message."""
142
- edit_button = f"{index}_edit"
143
- refresh_button = f"{index}_refresh"
144
- delete_button = f"{index}_delete"
145
-
146
- # Extract content from the message, handling the new event structure
147
- content = ""
148
- if isinstance(message, dict):
149
- if "content" in message:
150
- if isinstance(message["content"], dict) and "parts" in message["content"]:
151
- parts = message["content"]["parts"]
152
- if parts:
153
- if isinstance(parts, list):
154
- for part in parts:
155
- if isinstance(part, dict) and "text" in part:
156
- content += (
157
- part["text"] if part["text"] is not None else ""
158
- )
159
- elif isinstance(parts, str):
160
- content = parts
161
- elif isinstance(message["content"], str):
162
- content = message["content"]
163
-
164
- with col1:
165
- st.button(label="✎", key=edit_button, type="primary")
166
- if message["content"]["role"] == "user":
167
- with col2:
168
- st.button(
169
- label="⟳",
170
- key=refresh_button,
171
- type="primary",
172
- on_click=partial(MessageEditing.refresh_message, st, index, content),
173
- )
174
- with col3:
175
- st.button(
176
- label="X",
177
- key=delete_button,
178
- type="primary",
179
- on_click=partial(MessageEditing.delete_message, st, index),
180
- )
181
-
182
- if st.session_state[edit_button]:
183
- st.text_area(
184
- "Edit your message:",
185
- value=content,
186
- key=f"edit_box_{index}",
187
- on_change=partial(
188
- MessageEditing.edit_message, st, index, message["content"]["role"]
189
- ),
190
- )
191
-
192
-
193
- def display_tool_output(
194
- tool_call_input: dict[str, Any], tool_call_output: dict[str, Any]
195
- ) -> None:
196
- """Display the input and output of a tool call in an expander."""
197
- tool_expander = st.expander(label="Tool Calls:", expanded=False)
198
- with tool_expander:
199
- msg = (
200
- f"\n\nEnding tool: `{tool_call_input}` with\n **args:**\n"
201
- f"```\n{json.dumps(tool_call_input, indent=2)}\n```\n"
202
- f"\n\n**output:**\n "
203
- f"```\n{json.dumps(tool_call_output, indent=2)}\n```"
204
- )
205
- st.markdown(msg, unsafe_allow_html=True)
206
-
207
-
208
- def handle_user_input(side_bar: SideBar) -> None:
209
- """Process user input, generate AI response, and update chat history."""
210
- prompt = st.chat_input() or st.session_state.modified_prompt
211
- if prompt:
212
- st.session_state.modified_prompt = None
213
- parts = get_parts_from_files(
214
- upload_gcs_checkbox=st.session_state.checkbox_state,
215
- uploaded_files=side_bar.uploaded_files,
216
- gcs_uris=side_bar.gcs_uris,
217
- )
218
- st.session_state["gcs_uris_to_be_sent"] = ""
219
- parts.append(types.Part(text=prompt))
220
- st.session_state.user_chats[st.session_state["session_id"]]["messages"].append(
221
- Event(
222
- content=types.Content(parts=parts, role="user"), author="user"
223
- ).model_dump()
224
- )
225
- display_user_input(parts)
226
- generate_ai_response(
227
- remote_agent_engine_id=side_bar.remote_agent_engine_id,
228
- agent_callable_path=side_bar.agent_callable_path,
229
- url=side_bar.url_input_field,
230
- authenticate_request=side_bar.should_authenticate_request,
231
- )
232
- update_chat_title()
233
- if len(parts) > 1:
234
- st.session_state.uploader_key += 1
235
- st.rerun()
236
-
237
-
238
- def display_user_input(parts: Sequence[dict[str, Any]]) -> None:
239
- """Display the user's input in the chat interface."""
240
- human_message = st.chat_message("human")
241
- with human_message:
242
- existing_user_input = format_content(parts)
243
- st.markdown(existing_user_input, unsafe_allow_html=True)
244
-
245
-
246
- def generate_ai_response(
247
- remote_agent_engine_id: str | None = None,
248
- agent_callable_path: str | None = None,
249
- url: str | None = None,
250
- authenticate_request: bool = False,
251
- ) -> None:
252
- """Generate and display the AI's response to the user's input."""
253
- ai_message = st.chat_message("ai")
254
- with ai_message:
255
- status = st.status("Generating answer🤖")
256
- stream_handler = StreamHandler(st=st)
257
- client = Client(
258
- remote_agent_engine_id=remote_agent_engine_id,
259
- agent_callable_path=agent_callable_path,
260
- url=url,
261
- authenticate_request=authenticate_request,
262
- )
263
- get_chain_response(st=st, client=client, stream_handler=stream_handler)
264
- status.update(label="Finished!", state="complete", expanded=False)
265
-
266
-
267
- def update_chat_title() -> None:
268
- """Update the chat title if it's currently empty."""
269
- if (
270
- st.session_state.user_chats[st.session_state["session_id"]]["title"]
271
- == EMPTY_CHAT_NAME
272
- ):
273
- st.session_state.session_db.set_title(
274
- st.session_state.user_chats[st.session_state["session_id"]]
275
- )
276
- st.session_state.session_db.upsert_session(
277
- st.session_state.user_chats[st.session_state["session_id"]]
278
- )
279
-
280
-
281
- def display_feedback(side_bar: SideBar) -> None:
282
- """Display a feedback component and log the feedback if provided."""
283
- if st.session_state.invocation_id is not None:
284
- feedback = streamlit_feedback(
285
- feedback_type="faces",
286
- optional_text_label="[Optional] Please provide an explanation",
287
- key=f"feedback-{st.session_state.invocation_id}",
288
- )
289
- if feedback is not None:
290
- client = Client(
291
- remote_agent_engine_id=side_bar.remote_agent_engine_id,
292
- agent_callable_path=side_bar.agent_callable_path,
293
- url=side_bar.url_input_field,
294
- authenticate_request=side_bar.should_authenticate_request,
295
- )
296
- client.log_feedback(
297
- feedback_dict=feedback,
298
- invocation_id=st.session_state.invocation_id,
299
- )
300
-
301
-
302
- def main() -> None:
303
- """Main function to set up and run the Streamlit app."""
304
- setup_page()
305
- initialize_session_state()
306
- side_bar = SideBar(st=st)
307
- side_bar.init_side_bar()
308
- display_messages()
309
- handle_user_input(side_bar=side_bar)
310
- display_feedback(side_bar=side_bar)
311
-
312
-
313
- if __name__ == "__main__":
314
- main()
@@ -1,37 +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
-
15
- MARKDOWN_STR = """
16
- <style>
17
- button[kind="primary"] {
18
- background: none!important;
19
- border: 0;
20
- padding: 20!important;
21
- color: grey !important;
22
- text-decoration: none;
23
- cursor: pointer;
24
- border: none !important;
25
- # float: right;
26
- }
27
- button[kind="primary"]:hover {
28
- text-decoration: none;
29
- color: white !important;
30
- }
31
- button[kind="primary"]:focus {
32
- outline: none !important;
33
- box-shadow: none !important;
34
- color: !important;
35
- }
36
- </style>
37
- """
@@ -1,84 +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
-
15
- import os
16
- from pathlib import Path
17
- from typing import Any
18
-
19
- import yaml
20
- from google.adk.events.event import Event
21
-
22
- SAVED_CHAT_PATH = str(os.getcwd()) + "/.saved_chats"
23
-
24
-
25
- def clean_text(text: str) -> str:
26
- """Preprocess the input text by removing leading and trailing newlines."""
27
- if not text:
28
- return text
29
-
30
- if text.startswith("\n"):
31
- text = text[1:]
32
- if text.endswith("\n"):
33
- text = text[:-1]
34
- return text
35
-
36
-
37
- def sanitize_messages(
38
- messages: list[Event | dict],
39
- ) -> list[Event | dict]:
40
- """Preprocess and fix the content of messages."""
41
- for message in messages:
42
- # Handle Event objects
43
- if isinstance(message, Event):
44
- if hasattr(message.content, "parts"):
45
- for part in message.content.parts:
46
- if hasattr(part, "text") and part.text:
47
- part.text = clean_text(part.text)
48
- # Handle dictionary format
49
- elif isinstance(message, dict):
50
- if "content" in message:
51
- if (
52
- isinstance(message["content"], dict)
53
- and "parts" in message["content"]
54
- ):
55
- for part in message["content"]["parts"]:
56
- if isinstance(part, dict) and "text" in part:
57
- part["text"] = clean_text(part["text"])
58
- elif isinstance(message["content"], str):
59
- message["content"] = clean_text(message["content"])
60
- elif isinstance(message["content"], list):
61
- for part in message["content"]:
62
- if part.get("type") == "text":
63
- part["text"] = clean_text(part["text"])
64
- return messages
65
-
66
-
67
- def save_chat(st: Any) -> None:
68
- """Save the current chat session to a YAML file."""
69
- Path(SAVED_CHAT_PATH).mkdir(parents=True, exist_ok=True)
70
- session_id = st.session_state["session_id"]
71
- session = st.session_state.user_chats[session_id]
72
- messages = session.get("messages", [])
73
- if len(messages) > 0:
74
- session["messages"] = sanitize_messages(session["messages"])
75
- filename = f"{session_id}.yaml"
76
- with open(Path(SAVED_CHAT_PATH) / filename, "w") as file:
77
- yaml.dump(
78
- [session],
79
- file,
80
- allow_unicode=True,
81
- default_flow_style=False,
82
- encoding="utf-8",
83
- )
84
- st.toast(f"Chat saved to path: ↓ {Path(SAVED_CHAT_PATH) / filename}")