agent-starter-pack 0.18.2__py3-none-any.whl → 0.21.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.
- agent_starter_pack/agents/{langgraph_base_react → adk_a2a_base}/.template/templateconfig.yaml +5 -12
- agent_starter_pack/agents/adk_a2a_base/README.md +37 -0
- agent_starter_pack/{frontends/streamlit/frontend/style/app_markdown.py → agents/adk_a2a_base/app/__init__.py} +3 -23
- agent_starter_pack/agents/adk_a2a_base/app/agent.py +70 -0
- agent_starter_pack/agents/adk_a2a_base/notebooks/adk_a2a_app_testing.ipynb +583 -0
- agent_starter_pack/agents/{crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb → adk_a2a_base/notebooks/evaluating_adk_agent.ipynb} +163 -199
- agent_starter_pack/agents/adk_a2a_base/tests/integration/test_agent.py +58 -0
- agent_starter_pack/agents/adk_base/app/__init__.py +2 -2
- agent_starter_pack/agents/adk_base/app/agent.py +3 -0
- agent_starter_pack/agents/adk_base/notebooks/adk_app_testing.ipynb +13 -28
- agent_starter_pack/agents/adk_live/app/__init__.py +17 -0
- agent_starter_pack/agents/adk_live/app/agent.py +3 -0
- agent_starter_pack/agents/agentic_rag/app/__init__.py +2 -2
- agent_starter_pack/agents/agentic_rag/app/agent.py +3 -0
- agent_starter_pack/agents/agentic_rag/notebooks/adk_app_testing.ipynb +13 -28
- agent_starter_pack/agents/{crewai_coding_crew → langgraph_base}/.template/templateconfig.yaml +12 -9
- agent_starter_pack/agents/langgraph_base/README.md +30 -0
- agent_starter_pack/agents/langgraph_base/app/__init__.py +17 -0
- agent_starter_pack/agents/{langgraph_base_react → langgraph_base}/app/agent.py +4 -4
- agent_starter_pack/agents/{langgraph_base_react → langgraph_base}/tests/integration/test_agent.py +1 -1
- agent_starter_pack/base_template/.gitignore +4 -2
- agent_starter_pack/base_template/Makefile +110 -16
- agent_starter_pack/base_template/README.md +97 -12
- agent_starter_pack/base_template/deployment/terraform/dev/apis.tf +4 -6
- agent_starter_pack/base_template/deployment/terraform/dev/providers.tf +5 -1
- agent_starter_pack/base_template/deployment/terraform/dev/variables.tf +5 -3
- agent_starter_pack/base_template/deployment/terraform/dev/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +193 -0
- agent_starter_pack/base_template/deployment/terraform/github.tf +16 -9
- agent_starter_pack/base_template/deployment/terraform/locals.tf +7 -7
- agent_starter_pack/base_template/deployment/terraform/providers.tf +5 -1
- agent_starter_pack/base_template/deployment/terraform/sql/completions.sql +138 -0
- agent_starter_pack/base_template/deployment/terraform/storage.tf +0 -9
- agent_starter_pack/base_template/deployment/terraform/variables.tf +15 -19
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +20 -22
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +206 -0
- agent_starter_pack/base_template/pyproject.toml +5 -17
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +19 -4
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +36 -11
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +24 -5
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +44 -9
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/telemetry.py +96 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/{utils → app_utils}/typing.py +4 -6
- agent_starter_pack/{agents/crewai_coding_crew/app/crew/config/agents.yaml → base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/__init__.py } +9 -23
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/part_converter.py +138 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/__init__.py +13 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/a2a_agent_executor.py +265 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/task_result_aggregator.py +152 -0
- agent_starter_pack/cli/commands/create.py +40 -4
- agent_starter_pack/cli/commands/enhance.py +1 -1
- agent_starter_pack/cli/commands/register_gemini_enterprise.py +1070 -0
- agent_starter_pack/cli/main.py +2 -0
- agent_starter_pack/cli/utils/cicd.py +20 -4
- agent_starter_pack/cli/utils/template.py +257 -25
- agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +113 -16
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +2 -2
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +178 -9
- agent_starter_pack/deployment_targets/agent_engine/tests/{% if cookiecutter.is_a2a %}helpers.py{% else %}unused_helpers.py{% endif %} +138 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +193 -307
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/deploy.py +414 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/{utils → app_utils}/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +13 -14
- agent_starter_pack/deployment_targets/cloud_run/Dockerfile +4 -1
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +85 -86
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/service.tf +139 -107
- agent_starter_pack/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +228 -12
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/README.md +4 -4
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +92 -12
- agent_starter_pack/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/{server.py → fast_api_app.py} +194 -121
- agent_starter_pack/frontends/adk_live_react/frontend/package-lock.json +18 -18
- agent_starter_pack/frontends/adk_live_react/frontend/src/multimodal-live-types.ts +5 -3
- agent_starter_pack/resources/docs/adk-cheatsheet.md +198 -41
- agent_starter_pack/resources/locks/uv-adk_a2a_base-agent_engine.lock +4966 -0
- agent_starter_pack/resources/locks/uv-adk_a2a_base-cloud_run.lock +5011 -0
- agent_starter_pack/resources/locks/uv-adk_base-agent_engine.lock +1443 -709
- agent_starter_pack/resources/locks/uv-adk_base-cloud_run.lock +1058 -874
- agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +1443 -709
- agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +1058 -874
- agent_starter_pack/resources/locks/uv-agentic_rag-agent_engine.lock +1568 -749
- agent_starter_pack/resources/locks/uv-agentic_rag-cloud_run.lock +1123 -929
- agent_starter_pack/resources/locks/{uv-langgraph_base_react-agent_engine.lock → uv-langgraph_base-agent_engine.lock} +1714 -1689
- agent_starter_pack/resources/locks/{uv-langgraph_base_react-cloud_run.lock → uv-langgraph_base-cloud_run.lock} +1285 -2374
- agent_starter_pack/utils/watch_and_rebuild.py +1 -1
- {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/METADATA +3 -6
- {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/RECORD +89 -93
- agent_starter_pack-0.21.0.dist-info/entry_points.txt +2 -0
- llm.txt +4 -5
- agent_starter_pack/agents/crewai_coding_crew/README.md +0 -34
- agent_starter_pack/agents/crewai_coding_crew/app/agent.py +0 -47
- agent_starter_pack/agents/crewai_coding_crew/app/crew/config/tasks.yaml +0 -37
- agent_starter_pack/agents/crewai_coding_crew/app/crew/crew.py +0 -71
- agent_starter_pack/agents/crewai_coding_crew/tests/integration/test_agent.py +0 -47
- agent_starter_pack/agents/langgraph_base_react/README.md +0 -9
- agent_starter_pack/agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +0 -1574
- agent_starter_pack/base_template/deployment/terraform/dev/log_sinks.tf +0 -69
- agent_starter_pack/base_template/deployment/terraform/log_sinks.tf +0 -79
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +0 -155
- agent_starter_pack/cli/utils/register_gemini_enterprise.py +0 -406
- agent_starter_pack/deployment_targets/agent_engine/deployment/terraform/{% if not cookiecutter.is_adk_live %}service.tf{% else %}unused_service.tf{% endif %} +0 -82
- agent_starter_pack/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -1025
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +0 -99
- agent_starter_pack/frontends/streamlit/frontend/side_bar.py +0 -214
- agent_starter_pack/frontends/streamlit/frontend/streamlit_app.py +0 -265
- agent_starter_pack/frontends/streamlit/frontend/utils/chat_utils.py +0 -67
- agent_starter_pack/frontends/streamlit/frontend/utils/local_chat_history.py +0 -127
- agent_starter_pack/frontends/streamlit/frontend/utils/message_editing.py +0 -59
- agent_starter_pack/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -217
- agent_starter_pack/frontends/streamlit/frontend/utils/stream_handler.py +0 -310
- agent_starter_pack/frontends/streamlit/frontend/utils/title_summary.py +0 -94
- agent_starter_pack/resources/locks/uv-crewai_coding_crew-agent_engine.lock +0 -6650
- agent_starter_pack/resources/locks/uv-crewai_coding_crew-cloud_run.lock +0 -7825
- agent_starter_pack-0.18.2.dist-info/entry_points.txt +0 -3
- /agent_starter_pack/agents/{crewai_coding_crew → langgraph_base}/notebooks/evaluating_langgraph_agent.ipynb +0 -0
- /agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/{utils → app_utils}/gcs.py +0 -0
- {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,99 +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 datetime
|
|
16
|
-
import json
|
|
17
|
-
import logging
|
|
18
|
-
from typing import Any
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def parse_env_vars(env_vars_string: str | None) -> dict[str, str]:
|
|
22
|
-
"""Parse environment variables from a comma-separated KEY=VALUE string.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
env_vars_string: Comma-separated list of environment variables in KEY=VALUE format
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
Dictionary of environment variables with keys and values stripped of whitespace
|
|
29
|
-
"""
|
|
30
|
-
env_vars = {}
|
|
31
|
-
if env_vars_string:
|
|
32
|
-
for pair in env_vars_string.split(","):
|
|
33
|
-
if "=" in pair:
|
|
34
|
-
key, value = pair.split("=", 1)
|
|
35
|
-
env_vars[key.strip()] = value.strip()
|
|
36
|
-
else:
|
|
37
|
-
logging.warning(f"Skipping malformed environment variable pair: {pair}")
|
|
38
|
-
return env_vars
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def write_deployment_metadata(
|
|
42
|
-
remote_agent: Any,
|
|
43
|
-
metadata_file: str = "deployment_metadata.json",
|
|
44
|
-
) -> None:
|
|
45
|
-
"""Write deployment metadata to file.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
remote_agent: The deployed agent engine resource
|
|
49
|
-
metadata_file: Path to write the metadata JSON file
|
|
50
|
-
"""
|
|
51
|
-
metadata = {
|
|
52
|
-
"remote_agent_engine_id": remote_agent.api_resource.name,
|
|
53
|
-
"deployment_timestamp": datetime.datetime.now().isoformat(),
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
with open(metadata_file, "w") as f:
|
|
57
|
-
json.dump(metadata, f, indent=2)
|
|
58
|
-
|
|
59
|
-
logging.info(f"Agent Engine ID written to {metadata_file}")
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def print_deployment_success(
|
|
63
|
-
remote_agent: Any,
|
|
64
|
-
location: str,
|
|
65
|
-
project: str,
|
|
66
|
-
) -> None:
|
|
67
|
-
"""Print deployment success message with console URL.
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
remote_agent: The deployed agent engine resource
|
|
71
|
-
location: GCP region where the agent was deployed
|
|
72
|
-
project: GCP project ID
|
|
73
|
-
"""
|
|
74
|
-
# Extract agent engine ID and project number for console URL
|
|
75
|
-
resource_name_parts = remote_agent.api_resource.name.split("/")
|
|
76
|
-
agent_engine_id = resource_name_parts[-1]
|
|
77
|
-
project_number = resource_name_parts[1]
|
|
78
|
-
console_url = f"https://console.cloud.google.com/vertex-ai/agents/locations/{location}/agent-engines/{agent_engine_id}?project={project}"
|
|
79
|
-
|
|
80
|
-
{%- if cookiecutter.is_adk %}
|
|
81
|
-
{%- if cookiecutter.is_adk_live %}
|
|
82
|
-
print("\n✅ Deployment successful! Run your agent with: `make playground-remote`")
|
|
83
|
-
{%- else %}
|
|
84
|
-
print(
|
|
85
|
-
"\n✅ Deployment successful! Test your agent: notebooks/adk_app_testing.ipynb"
|
|
86
|
-
)
|
|
87
|
-
{%- endif %}
|
|
88
|
-
{%- else %}
|
|
89
|
-
print("\n✅ Deployment successful!")
|
|
90
|
-
{%- endif %}
|
|
91
|
-
service_account = remote_agent.api_resource.spec.service_account
|
|
92
|
-
if service_account:
|
|
93
|
-
print(f"Service Account: {service_account}")
|
|
94
|
-
else:
|
|
95
|
-
default_sa = (
|
|
96
|
-
f"service-{project_number}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"
|
|
97
|
-
)
|
|
98
|
-
print(f"Service Account: {default_sa}")
|
|
99
|
-
print(f"\n📊 View in console: {console_url}\n")
|
|
@@ -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", encoding="utf-8") 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.run_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.run_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.run_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.run_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,265 +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 langchain_core.messages import HumanMessage
|
|
24
|
-
from streamlit_feedback import streamlit_feedback
|
|
25
|
-
|
|
26
|
-
from frontend.side_bar import SideBar
|
|
27
|
-
from frontend.style.app_markdown import MARKDOWN_STR
|
|
28
|
-
from frontend.utils.local_chat_history import LocalChatMessageHistory
|
|
29
|
-
from frontend.utils.message_editing import MessageEditing
|
|
30
|
-
from frontend.utils.multimodal_utils import format_content, get_parts_from_files
|
|
31
|
-
from frontend.utils.stream_handler import Client, StreamHandler, get_chain_response
|
|
32
|
-
|
|
33
|
-
USER = "my_user"
|
|
34
|
-
EMPTY_CHAT_NAME = "Empty chat"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def setup_page() -> None:
|
|
38
|
-
"""Configure the Streamlit page settings."""
|
|
39
|
-
st.set_page_config(
|
|
40
|
-
page_title="Playground",
|
|
41
|
-
layout="wide",
|
|
42
|
-
initial_sidebar_state="auto",
|
|
43
|
-
menu_items=None,
|
|
44
|
-
)
|
|
45
|
-
st.title("Playground")
|
|
46
|
-
st.markdown(MARKDOWN_STR, unsafe_allow_html=True)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def initialize_session_state() -> None:
|
|
50
|
-
"""Initialize the session state with default values."""
|
|
51
|
-
if "user_chats" not in st.session_state:
|
|
52
|
-
st.session_state["session_id"] = str(uuid.uuid4())
|
|
53
|
-
st.session_state.uploader_key = 0
|
|
54
|
-
st.session_state.run_id = None
|
|
55
|
-
st.session_state.user_id = USER
|
|
56
|
-
st.session_state["gcs_uris_to_be_sent"] = ""
|
|
57
|
-
st.session_state.modified_prompt = None
|
|
58
|
-
st.session_state.session_db = LocalChatMessageHistory(
|
|
59
|
-
session_id=st.session_state["session_id"],
|
|
60
|
-
user_id=st.session_state["user_id"],
|
|
61
|
-
)
|
|
62
|
-
st.session_state.user_chats = (
|
|
63
|
-
st.session_state.session_db.get_all_conversations()
|
|
64
|
-
)
|
|
65
|
-
st.session_state.user_chats[st.session_state["session_id"]] = {
|
|
66
|
-
"title": EMPTY_CHAT_NAME,
|
|
67
|
-
"messages": [],
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def display_messages() -> None:
|
|
72
|
-
"""Display all messages in the current chat session."""
|
|
73
|
-
messages = st.session_state.user_chats[st.session_state["session_id"]]["messages"]
|
|
74
|
-
tool_calls_map = {} # Map tool_call_id to tool call input
|
|
75
|
-
|
|
76
|
-
for i, message in enumerate(messages):
|
|
77
|
-
if message["type"] in ["ai", "human"] and message["content"]:
|
|
78
|
-
display_chat_message(message, i)
|
|
79
|
-
elif message.get("tool_calls"):
|
|
80
|
-
# Store each tool call input mapped by its ID
|
|
81
|
-
for tool_call in message["tool_calls"]:
|
|
82
|
-
tool_calls_map[tool_call["id"]] = tool_call
|
|
83
|
-
elif message["type"] == "tool":
|
|
84
|
-
# Look up the corresponding tool call input by ID
|
|
85
|
-
tool_call_id = message["tool_call_id"]
|
|
86
|
-
if tool_call_id in tool_calls_map:
|
|
87
|
-
display_tool_output(tool_calls_map[tool_call_id], message)
|
|
88
|
-
else:
|
|
89
|
-
st.error(f"Could not find tool call input for ID: {tool_call_id}")
|
|
90
|
-
else:
|
|
91
|
-
st.error(f"Unexpected message type: {message['type']}")
|
|
92
|
-
st.write("Full messages list:", messages)
|
|
93
|
-
raise ValueError(f"Unexpected message type: {message['type']}")
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def display_chat_message(message: dict[str, Any], index: int) -> None:
|
|
97
|
-
"""Display a single chat message with edit, refresh, and delete options."""
|
|
98
|
-
chat_message = st.chat_message(message["type"])
|
|
99
|
-
with chat_message:
|
|
100
|
-
st.markdown(format_content(message["content"]), unsafe_allow_html=True)
|
|
101
|
-
col1, col2, col3 = st.columns([2, 2, 94])
|
|
102
|
-
display_message_buttons(message, index, col1, col2, col3)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def display_message_buttons(
|
|
106
|
-
message: dict[str, Any], index: int, col1: Any, col2: Any, col3: Any
|
|
107
|
-
) -> None:
|
|
108
|
-
"""Display edit, refresh, and delete buttons for a chat message."""
|
|
109
|
-
edit_button = f"{index}_edit"
|
|
110
|
-
refresh_button = f"{index}_refresh"
|
|
111
|
-
delete_button = f"{index}_delete"
|
|
112
|
-
content = (
|
|
113
|
-
message["content"]
|
|
114
|
-
if isinstance(message["content"], str)
|
|
115
|
-
else message["content"][-1]["text"]
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
with col1:
|
|
119
|
-
st.button(label="✎", key=edit_button, type="primary")
|
|
120
|
-
if message["type"] == "human":
|
|
121
|
-
with col2:
|
|
122
|
-
st.button(
|
|
123
|
-
label="⟳",
|
|
124
|
-
key=refresh_button,
|
|
125
|
-
type="primary",
|
|
126
|
-
on_click=partial(MessageEditing.refresh_message, st, index, content),
|
|
127
|
-
)
|
|
128
|
-
with col3:
|
|
129
|
-
st.button(
|
|
130
|
-
label="X",
|
|
131
|
-
key=delete_button,
|
|
132
|
-
type="primary",
|
|
133
|
-
on_click=partial(MessageEditing.delete_message, st, index),
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
if st.session_state[edit_button]:
|
|
137
|
-
st.text_area(
|
|
138
|
-
"Edit your message:",
|
|
139
|
-
value=content,
|
|
140
|
-
key=f"edit_box_{index}",
|
|
141
|
-
on_change=partial(MessageEditing.edit_message, st, index, message["type"]),
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def display_tool_output(
|
|
146
|
-
tool_call_input: dict[str, Any], tool_call_output: dict[str, Any]
|
|
147
|
-
) -> None:
|
|
148
|
-
"""Display the input and output of a tool call in an expander."""
|
|
149
|
-
tool_expander = st.expander(label="Tool Calls:", expanded=False)
|
|
150
|
-
with tool_expander:
|
|
151
|
-
msg = (
|
|
152
|
-
f"\n\nEnding tool: `{tool_call_input}` with\n **args:**\n"
|
|
153
|
-
f"```\n{json.dumps(tool_call_input, indent=2)}\n```\n"
|
|
154
|
-
f"\n\n**output:**\n "
|
|
155
|
-
f"```\n{json.dumps(tool_call_output, indent=2)}\n```"
|
|
156
|
-
)
|
|
157
|
-
st.markdown(msg, unsafe_allow_html=True)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def handle_user_input(side_bar: SideBar) -> None:
|
|
161
|
-
"""Process user input, generate AI response, and update chat history."""
|
|
162
|
-
prompt = st.chat_input() or st.session_state.modified_prompt
|
|
163
|
-
if prompt:
|
|
164
|
-
st.session_state.modified_prompt = None
|
|
165
|
-
parts = get_parts_from_files(
|
|
166
|
-
upload_gcs_checkbox=st.session_state.checkbox_state,
|
|
167
|
-
uploaded_files=side_bar.uploaded_files,
|
|
168
|
-
gcs_uris=side_bar.gcs_uris,
|
|
169
|
-
)
|
|
170
|
-
st.session_state["gcs_uris_to_be_sent"] = ""
|
|
171
|
-
parts.append({"type": "text", "text": prompt})
|
|
172
|
-
st.session_state.user_chats[st.session_state["session_id"]]["messages"].append(
|
|
173
|
-
HumanMessage(content=parts).model_dump()
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
display_user_input(parts)
|
|
177
|
-
generate_ai_response(
|
|
178
|
-
remote_agent_engine_id=side_bar.remote_agent_engine_id,
|
|
179
|
-
agent_callable_path=side_bar.agent_callable_path,
|
|
180
|
-
url=side_bar.url_input_field,
|
|
181
|
-
authenticate_request=side_bar.should_authenticate_request,
|
|
182
|
-
)
|
|
183
|
-
update_chat_title()
|
|
184
|
-
if len(parts) > 1:
|
|
185
|
-
st.session_state.uploader_key += 1
|
|
186
|
-
st.rerun()
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def display_user_input(parts: Sequence[dict[str, Any]]) -> None:
|
|
190
|
-
"""Display the user's input in the chat interface."""
|
|
191
|
-
human_message = st.chat_message("human")
|
|
192
|
-
with human_message:
|
|
193
|
-
existing_user_input = format_content(parts)
|
|
194
|
-
st.markdown(existing_user_input, unsafe_allow_html=True)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def generate_ai_response(
|
|
198
|
-
remote_agent_engine_id: str | None = None,
|
|
199
|
-
agent_callable_path: str | None = None,
|
|
200
|
-
url: str | None = None,
|
|
201
|
-
authenticate_request: bool = False,
|
|
202
|
-
) -> None:
|
|
203
|
-
"""Generate and display the AI's response to the user's input."""
|
|
204
|
-
ai_message = st.chat_message("ai")
|
|
205
|
-
with ai_message:
|
|
206
|
-
status = st.status("Generating answer🤖")
|
|
207
|
-
stream_handler = StreamHandler(st=st)
|
|
208
|
-
client = Client(
|
|
209
|
-
remote_agent_engine_id=remote_agent_engine_id,
|
|
210
|
-
agent_callable_path=agent_callable_path,
|
|
211
|
-
url=url,
|
|
212
|
-
authenticate_request=authenticate_request,
|
|
213
|
-
)
|
|
214
|
-
get_chain_response(st=st, client=client, stream_handler=stream_handler)
|
|
215
|
-
status.update(label="Finished!", state="complete", expanded=False)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def update_chat_title() -> None:
|
|
219
|
-
"""Update the chat title if it's currently empty."""
|
|
220
|
-
if (
|
|
221
|
-
st.session_state.user_chats[st.session_state["session_id"]]["title"]
|
|
222
|
-
== EMPTY_CHAT_NAME
|
|
223
|
-
):
|
|
224
|
-
st.session_state.session_db.set_title(
|
|
225
|
-
st.session_state.user_chats[st.session_state["session_id"]]
|
|
226
|
-
)
|
|
227
|
-
st.session_state.session_db.upsert_session(
|
|
228
|
-
st.session_state.user_chats[st.session_state["session_id"]]
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def display_feedback(side_bar: SideBar) -> None:
|
|
233
|
-
"""Display a feedback component and log the feedback if provided."""
|
|
234
|
-
if st.session_state.run_id is not None:
|
|
235
|
-
feedback = streamlit_feedback(
|
|
236
|
-
feedback_type="faces",
|
|
237
|
-
optional_text_label="[Optional] Please provide an explanation",
|
|
238
|
-
key=f"feedback-{st.session_state.run_id}",
|
|
239
|
-
)
|
|
240
|
-
if feedback is not None:
|
|
241
|
-
client = Client(
|
|
242
|
-
remote_agent_engine_id=side_bar.remote_agent_engine_id,
|
|
243
|
-
agent_callable_path=side_bar.agent_callable_path,
|
|
244
|
-
url=side_bar.url_input_field,
|
|
245
|
-
authenticate_request=side_bar.should_authenticate_request,
|
|
246
|
-
)
|
|
247
|
-
client.log_feedback(
|
|
248
|
-
feedback_dict=feedback,
|
|
249
|
-
run_id=st.session_state.run_id,
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def main() -> None:
|
|
254
|
-
"""Main function to set up and run the Streamlit app."""
|
|
255
|
-
setup_page()
|
|
256
|
-
initialize_session_state()
|
|
257
|
-
side_bar = SideBar(st=st)
|
|
258
|
-
side_bar.init_side_bar()
|
|
259
|
-
display_messages()
|
|
260
|
-
handle_user_input(side_bar=side_bar)
|
|
261
|
-
display_feedback(side_bar=side_bar)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if __name__ == "__main__":
|
|
265
|
-
main()
|
|
@@ -1,67 +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
|
-
|
|
21
|
-
SAVED_CHAT_PATH = str(os.getcwd()) + "/.saved_chats"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def clean_text(text: str) -> str:
|
|
25
|
-
"""Preprocess the input text by removing leading and trailing newlines."""
|
|
26
|
-
if not text:
|
|
27
|
-
return text
|
|
28
|
-
|
|
29
|
-
if text.startswith("\n"):
|
|
30
|
-
text = text[1:]
|
|
31
|
-
if text.endswith("\n"):
|
|
32
|
-
text = text[:-1]
|
|
33
|
-
return text
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def sanitize_messages(
|
|
37
|
-
messages: list[dict[str, str | list[dict[str, str]]]],
|
|
38
|
-
) -> list[dict[str, str | list[dict[str, str]]]]:
|
|
39
|
-
"""Preprocess and fix the content of messages."""
|
|
40
|
-
for message in messages:
|
|
41
|
-
if isinstance(message["content"], list):
|
|
42
|
-
for part in message["content"]:
|
|
43
|
-
if part["type"] == "text":
|
|
44
|
-
part["text"] = clean_text(part["text"])
|
|
45
|
-
else:
|
|
46
|
-
message["content"] = clean_text(message["content"])
|
|
47
|
-
return messages
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def save_chat(st: Any) -> None:
|
|
51
|
-
"""Save the current chat session to a YAML file."""
|
|
52
|
-
Path(SAVED_CHAT_PATH).mkdir(parents=True, exist_ok=True)
|
|
53
|
-
session_id = st.session_state["session_id"]
|
|
54
|
-
session = st.session_state.user_chats[session_id]
|
|
55
|
-
messages = session.get("messages", [])
|
|
56
|
-
if len(messages) > 0:
|
|
57
|
-
session["messages"] = sanitize_messages(session["messages"])
|
|
58
|
-
filename = f"{session_id}.yaml"
|
|
59
|
-
with open(Path(SAVED_CHAT_PATH) / filename, "w", encoding="utf-8") as file:
|
|
60
|
-
yaml.dump(
|
|
61
|
-
[session],
|
|
62
|
-
file,
|
|
63
|
-
allow_unicode=True,
|
|
64
|
-
default_flow_style=False,
|
|
65
|
-
encoding="utf-8",
|
|
66
|
-
)
|
|
67
|
-
st.toast(f"Chat saved to path: ↓ {Path(SAVED_CHAT_PATH) / filename}")
|