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.
Files changed (114) hide show
  1. agent_starter_pack/agents/{langgraph_base_react → adk_a2a_base}/.template/templateconfig.yaml +5 -12
  2. agent_starter_pack/agents/adk_a2a_base/README.md +37 -0
  3. agent_starter_pack/{frontends/streamlit/frontend/style/app_markdown.py → agents/adk_a2a_base/app/__init__.py} +3 -23
  4. agent_starter_pack/agents/adk_a2a_base/app/agent.py +70 -0
  5. agent_starter_pack/agents/adk_a2a_base/notebooks/adk_a2a_app_testing.ipynb +583 -0
  6. agent_starter_pack/agents/{crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb → adk_a2a_base/notebooks/evaluating_adk_agent.ipynb} +163 -199
  7. agent_starter_pack/agents/adk_a2a_base/tests/integration/test_agent.py +58 -0
  8. agent_starter_pack/agents/adk_base/app/__init__.py +2 -2
  9. agent_starter_pack/agents/adk_base/app/agent.py +3 -0
  10. agent_starter_pack/agents/adk_base/notebooks/adk_app_testing.ipynb +13 -28
  11. agent_starter_pack/agents/adk_live/app/__init__.py +17 -0
  12. agent_starter_pack/agents/adk_live/app/agent.py +3 -0
  13. agent_starter_pack/agents/agentic_rag/app/__init__.py +2 -2
  14. agent_starter_pack/agents/agentic_rag/app/agent.py +3 -0
  15. agent_starter_pack/agents/agentic_rag/notebooks/adk_app_testing.ipynb +13 -28
  16. agent_starter_pack/agents/{crewai_coding_crew → langgraph_base}/.template/templateconfig.yaml +12 -9
  17. agent_starter_pack/agents/langgraph_base/README.md +30 -0
  18. agent_starter_pack/agents/langgraph_base/app/__init__.py +17 -0
  19. agent_starter_pack/agents/{langgraph_base_react → langgraph_base}/app/agent.py +4 -4
  20. agent_starter_pack/agents/{langgraph_base_react → langgraph_base}/tests/integration/test_agent.py +1 -1
  21. agent_starter_pack/base_template/.gitignore +4 -2
  22. agent_starter_pack/base_template/Makefile +110 -16
  23. agent_starter_pack/base_template/README.md +97 -12
  24. agent_starter_pack/base_template/deployment/terraform/dev/apis.tf +4 -6
  25. agent_starter_pack/base_template/deployment/terraform/dev/providers.tf +5 -1
  26. agent_starter_pack/base_template/deployment/terraform/dev/variables.tf +5 -3
  27. agent_starter_pack/base_template/deployment/terraform/dev/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +193 -0
  28. agent_starter_pack/base_template/deployment/terraform/github.tf +16 -9
  29. agent_starter_pack/base_template/deployment/terraform/locals.tf +7 -7
  30. agent_starter_pack/base_template/deployment/terraform/providers.tf +5 -1
  31. agent_starter_pack/base_template/deployment/terraform/sql/completions.sql +138 -0
  32. agent_starter_pack/base_template/deployment/terraform/storage.tf +0 -9
  33. agent_starter_pack/base_template/deployment/terraform/variables.tf +15 -19
  34. 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
  35. agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +206 -0
  36. agent_starter_pack/base_template/pyproject.toml +5 -17
  37. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +19 -4
  38. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +36 -11
  39. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +24 -5
  40. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +44 -9
  41. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/telemetry.py +96 -0
  42. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/{utils → app_utils}/typing.py +4 -6
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. agent_starter_pack/cli/commands/create.py +40 -4
  49. agent_starter_pack/cli/commands/enhance.py +1 -1
  50. agent_starter_pack/cli/commands/register_gemini_enterprise.py +1070 -0
  51. agent_starter_pack/cli/main.py +2 -0
  52. agent_starter_pack/cli/utils/cicd.py +20 -4
  53. agent_starter_pack/cli/utils/template.py +257 -25
  54. agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +113 -16
  55. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +2 -2
  56. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +178 -9
  57. agent_starter_pack/deployment_targets/agent_engine/tests/{% if cookiecutter.is_a2a %}helpers.py{% else %}unused_helpers.py{% endif %} +138 -0
  58. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +193 -307
  59. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/deploy.py +414 -0
  60. 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
  61. agent_starter_pack/deployment_targets/cloud_run/Dockerfile +4 -1
  62. agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +85 -86
  63. agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/service.tf +139 -107
  64. agent_starter_pack/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +228 -12
  65. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/README.md +4 -4
  66. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +92 -12
  67. agent_starter_pack/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/{server.py → fast_api_app.py} +194 -121
  68. agent_starter_pack/frontends/adk_live_react/frontend/package-lock.json +18 -18
  69. agent_starter_pack/frontends/adk_live_react/frontend/src/multimodal-live-types.ts +5 -3
  70. agent_starter_pack/resources/docs/adk-cheatsheet.md +198 -41
  71. agent_starter_pack/resources/locks/uv-adk_a2a_base-agent_engine.lock +4966 -0
  72. agent_starter_pack/resources/locks/uv-adk_a2a_base-cloud_run.lock +5011 -0
  73. agent_starter_pack/resources/locks/uv-adk_base-agent_engine.lock +1443 -709
  74. agent_starter_pack/resources/locks/uv-adk_base-cloud_run.lock +1058 -874
  75. agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +1443 -709
  76. agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +1058 -874
  77. agent_starter_pack/resources/locks/uv-agentic_rag-agent_engine.lock +1568 -749
  78. agent_starter_pack/resources/locks/uv-agentic_rag-cloud_run.lock +1123 -929
  79. agent_starter_pack/resources/locks/{uv-langgraph_base_react-agent_engine.lock → uv-langgraph_base-agent_engine.lock} +1714 -1689
  80. agent_starter_pack/resources/locks/{uv-langgraph_base_react-cloud_run.lock → uv-langgraph_base-cloud_run.lock} +1285 -2374
  81. agent_starter_pack/utils/watch_and_rebuild.py +1 -1
  82. {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/METADATA +3 -6
  83. {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/RECORD +89 -93
  84. agent_starter_pack-0.21.0.dist-info/entry_points.txt +2 -0
  85. llm.txt +4 -5
  86. agent_starter_pack/agents/crewai_coding_crew/README.md +0 -34
  87. agent_starter_pack/agents/crewai_coding_crew/app/agent.py +0 -47
  88. agent_starter_pack/agents/crewai_coding_crew/app/crew/config/tasks.yaml +0 -37
  89. agent_starter_pack/agents/crewai_coding_crew/app/crew/crew.py +0 -71
  90. agent_starter_pack/agents/crewai_coding_crew/tests/integration/test_agent.py +0 -47
  91. agent_starter_pack/agents/langgraph_base_react/README.md +0 -9
  92. agent_starter_pack/agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +0 -1574
  93. agent_starter_pack/base_template/deployment/terraform/dev/log_sinks.tf +0 -69
  94. agent_starter_pack/base_template/deployment/terraform/log_sinks.tf +0 -79
  95. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +0 -155
  96. agent_starter_pack/cli/utils/register_gemini_enterprise.py +0 -406
  97. agent_starter_pack/deployment_targets/agent_engine/deployment/terraform/{% if not cookiecutter.is_adk_live %}service.tf{% else %}unused_service.tf{% endif %} +0 -82
  98. agent_starter_pack/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -1025
  99. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +0 -99
  100. agent_starter_pack/frontends/streamlit/frontend/side_bar.py +0 -214
  101. agent_starter_pack/frontends/streamlit/frontend/streamlit_app.py +0 -265
  102. agent_starter_pack/frontends/streamlit/frontend/utils/chat_utils.py +0 -67
  103. agent_starter_pack/frontends/streamlit/frontend/utils/local_chat_history.py +0 -127
  104. agent_starter_pack/frontends/streamlit/frontend/utils/message_editing.py +0 -59
  105. agent_starter_pack/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -217
  106. agent_starter_pack/frontends/streamlit/frontend/utils/stream_handler.py +0 -310
  107. agent_starter_pack/frontends/streamlit/frontend/utils/title_summary.py +0 -94
  108. agent_starter_pack/resources/locks/uv-crewai_coding_crew-agent_engine.lock +0 -6650
  109. agent_starter_pack/resources/locks/uv-crewai_coding_crew-cloud_run.lock +0 -7825
  110. agent_starter_pack-0.18.2.dist-info/entry_points.txt +0 -3
  111. /agent_starter_pack/agents/{crewai_coding_crew → langgraph_base}/notebooks/evaluating_langgraph_agent.ipynb +0 -0
  112. /agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/{utils → app_utils}/gcs.py +0 -0
  113. {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/WHEEL +0 -0
  114. {agent_starter_pack-0.18.2.dist-info → agent_starter_pack-0.21.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,127 +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 datetime import datetime
17
-
18
- import yaml
19
- from langchain_core.chat_history import BaseChatMessageHistory
20
-
21
- from frontend.utils.title_summary import chain_title
22
-
23
-
24
- class LocalChatMessageHistory(BaseChatMessageHistory):
25
- """Manages local storage and retrieval of chat message history."""
26
-
27
- def __init__(
28
- self,
29
- user_id: str,
30
- session_id: str = "default",
31
- base_dir: str = ".streamlit_chats",
32
- ) -> None:
33
- self.user_id = user_id
34
- self.session_id = session_id
35
- self.base_dir = base_dir
36
- self.user_dir = os.path.join(self.base_dir, self.user_id)
37
- self.session_file = os.path.join(self.user_dir, f"{session_id}.yaml")
38
-
39
- os.makedirs(self.user_dir, exist_ok=True)
40
-
41
- def get_session(self, session_id: str) -> None:
42
- """Updates the session ID and file path for the current session."""
43
- self.session_id = session_id
44
- self.session_file = os.path.join(self.user_dir, f"{session_id}.yaml")
45
-
46
- def get_all_conversations(self) -> dict[str, dict]:
47
- """Retrieves all conversations for the current user."""
48
- conversations = {}
49
- for filename in os.listdir(self.user_dir):
50
- if filename.endswith(".yaml"):
51
- file_path = os.path.join(self.user_dir, filename)
52
- with open(file_path, encoding="utf-8") as f:
53
- conversation = yaml.safe_load(f)
54
- if not isinstance(conversation, list) or len(conversation) > 1:
55
- raise ValueError(
56
- f"""Invalid format in {file_path}.
57
- YAML file can only contain one conversation with the following
58
- structure.
59
- - messages:
60
- - content: [message text]
61
- - type: (human or ai)"""
62
- )
63
- conversation = conversation[0]
64
- if "title" not in conversation:
65
- conversation["title"] = filename
66
- conversations[filename[:-5]] = conversation
67
- return dict(
68
- sorted(conversations.items(), key=lambda x: x[1].get("update_time", ""))
69
- )
70
-
71
- def upsert_session(self, session: dict) -> None:
72
- """Updates or inserts a session into the local storage."""
73
- session["update_time"] = datetime.now().isoformat()
74
- with open(self.session_file, "w", encoding="utf-8") as f:
75
- yaml.dump(
76
- [session],
77
- f,
78
- allow_unicode=True,
79
- default_flow_style=False,
80
- encoding="utf-8",
81
- )
82
-
83
- def set_title(self, session: dict) -> None:
84
- """
85
- Set the title for the given session.
86
-
87
- This method generates a title for the session based on its messages.
88
- If the session has messages, it appends a special message to prompt
89
- for title creation, generates the title using a title chain, and
90
- updates the session with the new title.
91
-
92
- Args:
93
- session (dict): A dictionary containing session information,
94
- including messages.
95
-
96
- Returns:
97
- None
98
- """
99
- if session["messages"]:
100
- messages = session["messages"] + [
101
- {
102
- "type": "human",
103
- "content": "End of conversation - Create one single title",
104
- }
105
- ]
106
- # Remove the tool calls from conversation
107
- messages = [
108
- msg
109
- for msg in messages
110
- if msg["type"] in ("ai", "human") and isinstance(msg["content"], str)
111
- ]
112
- # Convert messages to the format expected by chain_title.invoke
113
- messages = {"messages": messages}
114
-
115
- response = chain_title.invoke(messages)
116
- title = (
117
- response.content.strip()
118
- if isinstance(response.content, str)
119
- else str(response.content)
120
- )
121
- session["title"] = title
122
- self.upsert_session(session)
123
-
124
- def clear(self) -> None:
125
- """Removes the current session file if it exists."""
126
- if os.path.exists(self.session_file):
127
- os.remove(self.session_file)
@@ -1,59 +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
- # fmt: off
16
-
17
- from typing import Any
18
-
19
-
20
- class MessageEditing:
21
- """Provides methods for editing, refreshing, and deleting chat messages."""
22
-
23
- @staticmethod
24
- def edit_message(st: Any, button_idx: int, message_type: str) -> None:
25
- """Edit a message in the chat history."""
26
- button_id = f"edit_box_{button_idx}"
27
- if message_type == "human":
28
- messages = st.session_state.user_chats[st.session_state["session_id"]][
29
- "messages"
30
- ]
31
- st.session_state.user_chats[st.session_state["session_id"]][
32
- "messages"
33
- ] = messages[:button_idx]
34
- st.session_state.modified_prompt = st.session_state[button_id]
35
- else:
36
- st.session_state.user_chats[st.session_state["session_id"]]["messages"][
37
- button_idx
38
- ]["content"] = st.session_state[button_id]
39
-
40
- @staticmethod
41
- def refresh_message(st: Any, button_idx: int, content: str) -> None:
42
- """Refresh a message in the chat history."""
43
- messages = st.session_state.user_chats[st.session_state["session_id"]][
44
- "messages"
45
- ]
46
- st.session_state.user_chats[st.session_state["session_id"]][
47
- "messages"
48
- ] = messages[:button_idx]
49
- st.session_state.modified_prompt = content
50
-
51
- @staticmethod
52
- def delete_message(st: Any, button_idx: int) -> None:
53
- """Delete a message from the chat history."""
54
- messages = st.session_state.user_chats[st.session_state["session_id"]][
55
- "messages"
56
- ]
57
- st.session_state.user_chats[st.session_state["session_id"]][
58
- "messages"
59
- ] = messages[:button_idx]
@@ -1,217 +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 base64
16
- from typing import Any
17
- from urllib.parse import quote
18
-
19
- from google.cloud import storage
20
-
21
- HELP_MESSAGE_MULTIMODALITY = (
22
- "For Gemini models to access the URIs you provide, store them in "
23
- "Google Cloud Storage buckets within the same project used by Gemini."
24
- )
25
-
26
- HELP_GCS_CHECKBOX = (
27
- "Enabling GCS upload will increase the app observability by avoiding"
28
- " forwarding and logging large byte strings within the app."
29
- )
30
-
31
-
32
- def format_content(content: str | list[dict[str, Any]]) -> str:
33
- """Formats content as a string, handling both text and multimedia inputs."""
34
- if isinstance(content, str):
35
- return content
36
- if len(content) == 1 and content[0]["type"] == "text":
37
- return content[0]["text"]
38
- markdown = """Media:
39
- """
40
- text = ""
41
- for part in content:
42
- if part["type"] == "text":
43
- text = part["text"]
44
- # Local Images:
45
- if part["type"] == "image_url":
46
- image_url = part["image_url"]["url"]
47
- image_markdown = f'<img src="{image_url}" width="100">'
48
- markdown = (
49
- markdown
50
- + f"""
51
- - {image_markdown}
52
- """
53
- )
54
- if part["type"] == "media":
55
- # Local other media
56
- if "data" in part:
57
- markdown = markdown + f"- Local media: {part['file_name']}\n"
58
- # From GCS:
59
- if "file_uri" in part:
60
- # GCS images
61
- if "image" in part["mime_type"]:
62
- image_url = gs_uri_to_https_url(part["file_uri"])
63
- image_markdown = f'<img src="{image_url}" width="100">'
64
- markdown = (
65
- markdown
66
- + f"""
67
- - {image_markdown}
68
- """
69
- )
70
- # GCS other media
71
- else:
72
- image_url = gs_uri_to_https_url(part["file_uri"])
73
- markdown = (
74
- markdown + f"- Remote media: "
75
- f"[{part['file_uri']}]({image_url})\n"
76
- )
77
- markdown = (
78
- markdown
79
- + f"""
80
-
81
- {text}"""
82
- )
83
- return markdown
84
-
85
-
86
- def get_gcs_blob_mime_type(gcs_uri: str) -> str | None:
87
- """Fetches the MIME type (content type) of a Google Cloud Storage blob.
88
-
89
- Args:
90
- gcs_uri (str): The GCS URI of the blob in the format "gs://bucket-name/object-name".
91
-
92
- Returns:
93
- str: The MIME type of the blob (e.g., "image/jpeg", "text/plain") if found,
94
- or None if the blob does not exist or an error occurs.
95
- """
96
- storage_client = storage.Client()
97
-
98
- try:
99
- bucket_name, object_name = gcs_uri.replace("gs://", "").split("/", 1)
100
-
101
- bucket = storage_client.bucket(bucket_name)
102
- blob = bucket.blob(object_name)
103
- blob.reload()
104
- return blob.content_type
105
- except Exception as e:
106
- print(f"Error retrieving MIME type for {gcs_uri}: {e}")
107
- return None # Indicate failure
108
-
109
-
110
- def get_parts_from_files(
111
- upload_gcs_checkbox: bool, uploaded_files: list[Any], gcs_uris: str
112
- ) -> list[dict[str, Any]]:
113
- """Processes uploaded files and GCS URIs to create a list of content parts."""
114
- parts = []
115
- # read from local directly
116
- if not upload_gcs_checkbox:
117
- for uploaded_file in uploaded_files:
118
- im_bytes = uploaded_file.read()
119
- if "image" in uploaded_file.type:
120
- content = {
121
- "type": "image_url",
122
- "image_url": {
123
- "url": f"data:{uploaded_file.type};base64,"
124
- f"{base64.b64encode(im_bytes).decode('utf-8')}"
125
- },
126
- "file_name": uploaded_file.name,
127
- }
128
- else:
129
- content = {
130
- "type": "media",
131
- "data": base64.b64encode(im_bytes).decode("utf-8"),
132
- "file_name": uploaded_file.name,
133
- "mime_type": uploaded_file.type,
134
- }
135
-
136
- parts.append(content)
137
- if gcs_uris != "":
138
- for uri in gcs_uris.split(","):
139
- content = {
140
- "type": "media",
141
- "file_uri": uri,
142
- "mime_type": get_gcs_blob_mime_type(uri),
143
- }
144
- parts.append(content)
145
- return parts
146
-
147
-
148
- def upload_bytes_to_gcs(
149
- bucket_name: str,
150
- blob_name: str,
151
- file_bytes: bytes,
152
- content_type: str | None = None,
153
- ) -> str:
154
- """Uploads a bytes object to Google Cloud Storage and returns the GCS URI.
155
-
156
- Args:
157
- bucket_name: The name of the GCS bucket.
158
- blob_name: The desired name for the uploaded file in GCS.
159
- file_bytes: The file's content as a bytes object.
160
- content_type (optional): The MIME type of the file (e.g., "image/png").
161
- If not provided, GCS will try to infer it.
162
-
163
- Returns:
164
- str: The GCS URI (gs://bucket_name/blob_name) of the uploaded file.
165
-
166
- Raises:
167
- GoogleCloudError: If there's an issue with the GCS operation.
168
- """
169
- storage_client = storage.Client()
170
- bucket = storage_client.bucket(bucket_name)
171
- blob = bucket.blob(blob_name)
172
- blob.upload_from_string(data=file_bytes, content_type=content_type)
173
- # Construct and return the GCS URI
174
- gcs_uri = f"gs://{bucket_name}/{blob_name}"
175
- return gcs_uri
176
-
177
-
178
- def gs_uri_to_https_url(gs_uri: str) -> str:
179
- """Converts a GS URI to an HTTPS URL without authentication.
180
-
181
- Args:
182
- gs_uri: The GS URI in the format gs://<bucket>/<object>.
183
-
184
- Returns:
185
- The corresponding HTTPS URL, or None if the GS URI is invalid.
186
- """
187
-
188
- if not gs_uri.startswith("gs://"):
189
- raise ValueError("Invalid GS URI format")
190
-
191
- gs_uri = gs_uri[5:]
192
-
193
- # Extract bucket and object names, then URL encode the object name
194
- bucket_name, object_name = gs_uri.split("/", 1)
195
- object_name = quote(object_name)
196
-
197
- # Construct the HTTPS URL
198
- https_url = f"https://storage.mtls.cloud.google.com/{bucket_name}/{object_name}"
199
- return https_url
200
-
201
-
202
- def upload_files_to_gcs(st: Any, bucket_name: str, files_to_upload: list[Any]) -> None:
203
- """Upload multiple files to Google Cloud Storage and store URIs in session state."""
204
- bucket_name = bucket_name.replace("gs://", "")
205
- uploaded_uris = []
206
- for file in files_to_upload:
207
- if file:
208
- file_bytes = file.read()
209
- gcs_uri = upload_bytes_to_gcs(
210
- bucket_name=bucket_name,
211
- blob_name=file.name,
212
- file_bytes=file_bytes,
213
- content_type=file.type,
214
- )
215
- uploaded_uris.append(gcs_uri)
216
- st.session_state.uploader_key += 1
217
- st.session_state["gcs_uris_to_be_sent"] = ",".join(uploaded_uris)