botrun-flow-lang 5.9.301__py3-none-any.whl → 5.10.82__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.
- botrun_flow_lang/api/auth_api.py +39 -39
- botrun_flow_lang/api/auth_utils.py +183 -183
- botrun_flow_lang/api/botrun_back_api.py +65 -65
- botrun_flow_lang/api/flow_api.py +3 -3
- botrun_flow_lang/api/hatch_api.py +481 -481
- botrun_flow_lang/api/langgraph_api.py +796 -796
- botrun_flow_lang/api/line_bot_api.py +1357 -1357
- botrun_flow_lang/api/model_api.py +300 -300
- botrun_flow_lang/api/rate_limit_api.py +32 -32
- botrun_flow_lang/api/routes.py +79 -79
- botrun_flow_lang/api/search_api.py +53 -53
- botrun_flow_lang/api/storage_api.py +316 -316
- botrun_flow_lang/api/subsidy_api.py +290 -290
- botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
- botrun_flow_lang/api/user_setting_api.py +70 -70
- botrun_flow_lang/api/version_api.py +31 -31
- botrun_flow_lang/api/youtube_api.py +26 -26
- botrun_flow_lang/constants.py +13 -13
- botrun_flow_lang/langgraph_agents/agents/agent_runner.py +174 -174
- botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
- botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
- botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +548 -542
- botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
- botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
- botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
- botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
- botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
- botrun_flow_lang/langgraph_agents/agents/util/img_util.py +294 -294
- botrun_flow_lang/langgraph_agents/agents/util/local_files.py +345 -345
- botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
- botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
- botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +160 -160
- botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
- botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
- botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
- botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
- botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
- botrun_flow_lang/llm_agent/llm_agent.py +19 -19
- botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
- botrun_flow_lang/log/.gitignore +2 -2
- botrun_flow_lang/main.py +61 -61
- botrun_flow_lang/main_fast.py +51 -51
- botrun_flow_lang/mcp_server/__init__.py +10 -10
- botrun_flow_lang/mcp_server/default_mcp.py +711 -711
- botrun_flow_lang/models/nodes/utils.py +205 -205
- botrun_flow_lang/models/token_usage.py +34 -34
- botrun_flow_lang/requirements.txt +21 -21
- botrun_flow_lang/services/base/firestore_base.py +30 -30
- botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
- botrun_flow_lang/services/hatch/hatch_fs_store.py +372 -372
- botrun_flow_lang/services/storage/storage_cs_store.py +202 -202
- botrun_flow_lang/services/storage/storage_factory.py +12 -12
- botrun_flow_lang/services/storage/storage_store.py +65 -65
- botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
- botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
- botrun_flow_lang/static/docs/tools/index.html +926 -926
- botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
- botrun_flow_lang/tests/api_stress_test.py +357 -357
- botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
- botrun_flow_lang/tests/test_botrun_app.py +46 -46
- botrun_flow_lang/tests/test_html_util.py +31 -31
- botrun_flow_lang/tests/test_img_analyzer.py +190 -190
- botrun_flow_lang/tests/test_img_util.py +39 -39
- botrun_flow_lang/tests/test_local_files.py +114 -114
- botrun_flow_lang/tests/test_mermaid_util.py +103 -103
- botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
- botrun_flow_lang/tests/test_plotly_util.py +151 -151
- botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
- botrun_flow_lang/tools/generate_docs.py +133 -133
- botrun_flow_lang/tools/templates/tools.html +153 -153
- botrun_flow_lang/utils/__init__.py +7 -7
- botrun_flow_lang/utils/botrun_logger.py +344 -344
- botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
- botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
- botrun_flow_lang/utils/google_drive_utils.py +654 -654
- botrun_flow_lang/utils/langchain_utils.py +324 -324
- botrun_flow_lang/utils/yaml_utils.py +9 -9
- {botrun_flow_lang-5.9.301.dist-info → botrun_flow_lang-5.10.82.dist-info}/METADATA +2 -2
- botrun_flow_lang-5.10.82.dist-info/RECORD +99 -0
- botrun_flow_lang-5.9.301.dist-info/RECORD +0 -99
- {botrun_flow_lang-5.9.301.dist-info → botrun_flow_lang-5.10.82.dist-info}/WHEEL +0 -0
|
@@ -1,376 +1,376 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Gemini Code Execution Tool for LangGraph
|
|
3
|
-
使用 Google Gemini API 的 code execution 功能執行 Python 程式碼
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
import json
|
|
8
|
-
from typing import ClassVar, Dict, Tuple, Any, Optional
|
|
9
|
-
from langchain_core.tools import BaseTool
|
|
10
|
-
from langchain_core.runnables import RunnableConfig
|
|
11
|
-
from langchain_core.messages import ToolMessage
|
|
12
|
-
from langgraph.config import get_stream_writer
|
|
13
|
-
from langgraph.types import StreamWriter
|
|
14
|
-
|
|
15
|
-
from botrun_flow_lang.constants import LANG_EN, LANG_ZH_TW
|
|
16
|
-
from botrun_flow_lang.utils.botrun_logger import BotrunLogger
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class GeminiCodeExecutionTool(BaseTool):
|
|
20
|
-
"""
|
|
21
|
-
使用 Google Gemini API 的 code execution 功能執行 Python 程式碼
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
# 類屬性定義
|
|
25
|
-
tool_name: ClassVar[str] = "gemini_code_execution"
|
|
26
|
-
|
|
27
|
-
# 定義多語言描述
|
|
28
|
-
descriptions: ClassVar[Dict[str, str]] = {
|
|
29
|
-
LANG_EN: """
|
|
30
|
-
Execute Python code using Google Gemini's code execution feature.
|
|
31
|
-
This tool allows Gemini to generate and run Python code iteratively until it produces a final output.
|
|
32
|
-
|
|
33
|
-
Capabilities:
|
|
34
|
-
- Mathematical calculations and data analysis
|
|
35
|
-
- Text processing and manipulation
|
|
36
|
-
- Using libraries: NumPy, Pandas, SymPy, and more
|
|
37
|
-
- Iterative code improvement based on execution results
|
|
38
|
-
- Maximum execution time: 30 seconds
|
|
39
|
-
|
|
40
|
-
Examples:
|
|
41
|
-
1. Mathematical calculation:
|
|
42
|
-
User: "Calculate the sum of the first 50 prime numbers"
|
|
43
|
-
gemini_code_execution("Calculate the sum of the first 50 prime numbers")
|
|
44
|
-
Returns: Code and result showing the sum is 5117
|
|
45
|
-
|
|
46
|
-
2. Text processing:
|
|
47
|
-
User: "Count word frequency in a text"
|
|
48
|
-
gemini_code_execution("Count word frequency in this text: 'The quick brown fox jumps over the lazy dog'")
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
code_request: Description of the code task to execute. Be specific about:
|
|
52
|
-
- What calculation or analysis to perform
|
|
53
|
-
- Input data (if any)
|
|
54
|
-
- Expected output format
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
dict: Contains:
|
|
58
|
-
- summary: Brief description of what was done
|
|
59
|
-
- code_blocks: List of executed code blocks
|
|
60
|
-
- results: Execution outputs
|
|
61
|
-
- images: List of generated images (if any)
|
|
62
|
-
- error: Error message (if any)
|
|
63
|
-
""",
|
|
64
|
-
LANG_ZH_TW: """
|
|
65
|
-
使用 Google Gemini 的程式碼執行功能執行 Python 程式碼。
|
|
66
|
-
此工具讓 Gemini 可以生成並執行 Python 程式碼,並根據結果反覆改進直到產生最終輸出。
|
|
67
|
-
|
|
68
|
-
功能特色:
|
|
69
|
-
- 數學計算和資料分析
|
|
70
|
-
- 文字處理和操作
|
|
71
|
-
- 支援函式庫:NumPy、Pandas、SymPy 等
|
|
72
|
-
- 根據執行結果反覆改進程式碼
|
|
73
|
-
- 最長執行時間:30 秒
|
|
74
|
-
|
|
75
|
-
使用範例:
|
|
76
|
-
1. 數學計算:
|
|
77
|
-
使用者:「計算前 50 個質數的總和」
|
|
78
|
-
gemini_code_execution("計算前 50 個質數的總和")
|
|
79
|
-
回傳:程式碼和結果顯示總和為 5117
|
|
80
|
-
|
|
81
|
-
2. 文字處理:
|
|
82
|
-
使用者:「計算文字中的詞頻」
|
|
83
|
-
gemini_code_execution("計算這段文字的詞頻:'The quick brown fox jumps over the lazy dog'")
|
|
84
|
-
|
|
85
|
-
參數:
|
|
86
|
-
code_request: 程式碼任務的描述。請具體說明:
|
|
87
|
-
- 要執行什麼計算或分析
|
|
88
|
-
- 輸入資料(如果有)
|
|
89
|
-
- 期望的輸出格式
|
|
90
|
-
|
|
91
|
-
Returns:
|
|
92
|
-
dict: 包含:
|
|
93
|
-
- summary: 執行內容的簡短描述
|
|
94
|
-
- code_blocks: 執行的程式碼區塊列表
|
|
95
|
-
- results: 執行輸出
|
|
96
|
-
- images: 產生的圖片列表(如果有)
|
|
97
|
-
- error: 錯誤訊息(如果有)
|
|
98
|
-
""",
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
# Pydantic 模型字段
|
|
102
|
-
name: str = "gemini_code_execution"
|
|
103
|
-
description: str = descriptions[LANG_EN]
|
|
104
|
-
lang: str = LANG_EN
|
|
105
|
-
response_format: str = "content_and_artifact" # 支援 artifact 回傳
|
|
106
|
-
|
|
107
|
-
@classmethod
|
|
108
|
-
def for_language(cls, lang: str = LANG_EN):
|
|
109
|
-
"""創建特定語言版本的工具實例"""
|
|
110
|
-
description = cls.descriptions.get(lang, cls.descriptions.get(LANG_EN))
|
|
111
|
-
return cls(name=cls.tool_name, description=description, lang=lang)
|
|
112
|
-
|
|
113
|
-
def _run(
|
|
114
|
-
self,
|
|
115
|
-
code_request: str,
|
|
116
|
-
config: RunnableConfig = None,
|
|
117
|
-
) -> Tuple[str, Dict[str, Any]]:
|
|
118
|
-
"""
|
|
119
|
-
執行 Gemini code execution 並返回結果
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
Tuple[str, Dict]: (給模型的訊息, 詳細執行結果)
|
|
123
|
-
"""
|
|
124
|
-
logger = BotrunLogger()
|
|
125
|
-
logger.info(
|
|
126
|
-
f"gemini_code_execution request",
|
|
127
|
-
code_request=code_request,
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
# 取得 stream writer 來輸出即時訊息
|
|
131
|
-
try:
|
|
132
|
-
writer = get_stream_writer()
|
|
133
|
-
has_writer = True
|
|
134
|
-
except Exception:
|
|
135
|
-
# 如果不在 LangGraph context 中,writer 會是 None
|
|
136
|
-
writer = None
|
|
137
|
-
has_writer = False
|
|
138
|
-
|
|
139
|
-
# 發送開始訊息
|
|
140
|
-
if has_writer:
|
|
141
|
-
writer(
|
|
142
|
-
{
|
|
143
|
-
"type": "status",
|
|
144
|
-
"message": f"🚀 Starting code execution for: {code_request[:100]}...",
|
|
145
|
-
}
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
try:
|
|
149
|
-
# 初始化 Gemini client
|
|
150
|
-
from google import genai
|
|
151
|
-
from google.genai import types
|
|
152
|
-
from google.oauth2 import service_account
|
|
153
|
-
|
|
154
|
-
credentials = service_account.Credentials.from_service_account_file(
|
|
155
|
-
os.getenv("GOOGLE_APPLICATION_CREDENTIALS_FOR_FASTAPI"),
|
|
156
|
-
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
client = genai.Client(
|
|
160
|
-
credentials=credentials,
|
|
161
|
-
project="scoop-386004",
|
|
162
|
-
location="us-central1",
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
# 發送 API 調用狀態
|
|
166
|
-
if has_writer:
|
|
167
|
-
writer(
|
|
168
|
-
{
|
|
169
|
-
"type": "progress",
|
|
170
|
-
"message": "📡 Calling Gemini API with code execution enabled...",
|
|
171
|
-
}
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
# 調用 Gemini API 並啟用 code execution
|
|
175
|
-
response = client.models.generate_content(
|
|
176
|
-
model="gemini-2.5-pro",
|
|
177
|
-
contents=code_request,
|
|
178
|
-
config=types.GenerateContentConfig(
|
|
179
|
-
tools=[types.Tool(code_execution=types.ToolCodeExecution())]
|
|
180
|
-
),
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
# 發送收到回應的狀態
|
|
184
|
-
if has_writer:
|
|
185
|
-
writer(
|
|
186
|
-
{
|
|
187
|
-
"type": "progress",
|
|
188
|
-
"message": "✅ Received response from Gemini API",
|
|
189
|
-
}
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
# 解析回應
|
|
193
|
-
executed_code = []
|
|
194
|
-
execution_results = []
|
|
195
|
-
summary_text = []
|
|
196
|
-
images = []
|
|
197
|
-
|
|
198
|
-
for part in response.candidates[0].content.parts:
|
|
199
|
-
if hasattr(part, "text") and part.text:
|
|
200
|
-
summary_text.append(part.text)
|
|
201
|
-
# 即時輸出 Gemini 的文字說明
|
|
202
|
-
if has_writer:
|
|
203
|
-
writer(
|
|
204
|
-
{
|
|
205
|
-
"type": "text",
|
|
206
|
-
"content": part.text,
|
|
207
|
-
"source": "gemini_explanation",
|
|
208
|
-
}
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
if hasattr(part, "executable_code") and part.executable_code:
|
|
212
|
-
code_info = {
|
|
213
|
-
"code": part.executable_code.code,
|
|
214
|
-
"language": getattr(part.executable_code, "language", "PYTHON"),
|
|
215
|
-
}
|
|
216
|
-
executed_code.append(code_info)
|
|
217
|
-
logger.debug(f"Executed code block", code_info=code_info)
|
|
218
|
-
|
|
219
|
-
# 即時輸出程式碼
|
|
220
|
-
if has_writer:
|
|
221
|
-
writer(
|
|
222
|
-
{
|
|
223
|
-
"type": "code",
|
|
224
|
-
"language": code_info["language"],
|
|
225
|
-
"content": code_info["code"],
|
|
226
|
-
"message": f"💻 Executing {code_info['language']} code block #{len(executed_code)}",
|
|
227
|
-
}
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
hasattr(part, "code_execution_result")
|
|
232
|
-
and part.code_execution_result
|
|
233
|
-
):
|
|
234
|
-
result_info = {
|
|
235
|
-
"output": part.code_execution_result.output,
|
|
236
|
-
"outcome": getattr(
|
|
237
|
-
part.code_execution_result, "outcome", "UNKNOWN"
|
|
238
|
-
),
|
|
239
|
-
}
|
|
240
|
-
execution_results.append(result_info)
|
|
241
|
-
logger.debug(f"Execution result", result_info=result_info)
|
|
242
|
-
|
|
243
|
-
# 即時輸出執行結果
|
|
244
|
-
if has_writer:
|
|
245
|
-
status_emoji = (
|
|
246
|
-
"✅" if result_info["outcome"] != "ERROR" else "❌"
|
|
247
|
-
)
|
|
248
|
-
writer(
|
|
249
|
-
{
|
|
250
|
-
"type": "execution_result",
|
|
251
|
-
"outcome": result_info["outcome"],
|
|
252
|
-
"output": result_info["output"],
|
|
253
|
-
"message": f"{status_emoji} Execution result (outcome: {result_info['outcome']})",
|
|
254
|
-
}
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
# 檢查是否有圖片輸出(Matplotlib 產生的圖表)
|
|
258
|
-
if hasattr(part, "inline_data") and part.inline_data:
|
|
259
|
-
image_info = {
|
|
260
|
-
"mime_type": part.inline_data.mime_type,
|
|
261
|
-
"data": part.inline_data.data, # Base64 encoded image
|
|
262
|
-
}
|
|
263
|
-
images.append(image_info)
|
|
264
|
-
logger.info(f"Generated image", mime_type=image_info["mime_type"])
|
|
265
|
-
|
|
266
|
-
# 即時輸出圖片生成通知
|
|
267
|
-
if has_writer:
|
|
268
|
-
writer(
|
|
269
|
-
{
|
|
270
|
-
"type": "image_generated",
|
|
271
|
-
"mime_type": image_info["mime_type"],
|
|
272
|
-
"message": f"📊 Generated visualization ({image_info['mime_type']})",
|
|
273
|
-
}
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
# 準備 artifact(詳細結果)
|
|
277
|
-
artifact = {
|
|
278
|
-
"executed_code": executed_code,
|
|
279
|
-
"execution_results": execution_results,
|
|
280
|
-
"images": images,
|
|
281
|
-
"full_response": str(response), # 保存完整回應以供調試
|
|
282
|
-
"has_visualization": len(images) > 0,
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
# 準備給模型的簡潔訊息
|
|
286
|
-
if summary_text:
|
|
287
|
-
content = " ".join(summary_text)
|
|
288
|
-
else:
|
|
289
|
-
content = "Code execution completed successfully."
|
|
290
|
-
|
|
291
|
-
if images:
|
|
292
|
-
content += f" Generated {len(images)} visualization(s)."
|
|
293
|
-
|
|
294
|
-
# 發送完成狀態
|
|
295
|
-
if has_writer:
|
|
296
|
-
writer(
|
|
297
|
-
{
|
|
298
|
-
"type": "completion",
|
|
299
|
-
"message": f"🎉 Code execution completed successfully!",
|
|
300
|
-
"stats": {
|
|
301
|
-
"code_blocks": len(executed_code),
|
|
302
|
-
"results": len(execution_results),
|
|
303
|
-
"images": len(images),
|
|
304
|
-
},
|
|
305
|
-
}
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
logger.info(
|
|
309
|
-
"gemini_code_execution completed",
|
|
310
|
-
num_code_blocks=len(executed_code),
|
|
311
|
-
num_results=len(execution_results),
|
|
312
|
-
num_images=len(images),
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
# 回傳 tuple 格式以支援 artifact
|
|
316
|
-
return content, artifact
|
|
317
|
-
|
|
318
|
-
except Exception as e:
|
|
319
|
-
error_msg = f"Code execution failed: {str(e)}"
|
|
320
|
-
logger.error(error_msg, error=str(e), exc_info=True)
|
|
321
|
-
|
|
322
|
-
# 發送錯誤訊息
|
|
323
|
-
if has_writer:
|
|
324
|
-
writer(
|
|
325
|
-
{
|
|
326
|
-
"type": "error",
|
|
327
|
-
"message": f"❌ {error_msg}",
|
|
328
|
-
"error_type": type(e).__name__,
|
|
329
|
-
}
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
error_artifact = {
|
|
333
|
-
"error": str(e),
|
|
334
|
-
"error_type": type(e).__name__,
|
|
335
|
-
"executed_code": [],
|
|
336
|
-
"execution_results": [],
|
|
337
|
-
"images": [],
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return error_msg, error_artifact
|
|
341
|
-
|
|
342
|
-
async def _arun(
|
|
343
|
-
self,
|
|
344
|
-
code_request: str,
|
|
345
|
-
config: RunnableConfig = None,
|
|
346
|
-
) -> Tuple[str, Dict[str, Any]]:
|
|
347
|
-
"""
|
|
348
|
-
異步版本的 _run 方法
|
|
349
|
-
目前直接調用同步版本,未來可以改為真正的異步實現
|
|
350
|
-
"""
|
|
351
|
-
# 注意:在 Python < 3.11 中,異步函數可能無法使用 get_stream_writer()
|
|
352
|
-
# 需要將 writer 作為參數傳入
|
|
353
|
-
# 目前這裡保持簡單,直接調用同步版本
|
|
354
|
-
return self._run(code_request, config)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
# 建立一個便利的函數裝飾器版本,供簡單使用
|
|
358
|
-
from langchain_core.tools import tool
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
@tool(response_format="content_and_artifact")
|
|
362
|
-
def gemini_code_execution(
|
|
363
|
-
code_request: str,
|
|
364
|
-
config: RunnableConfig = None,
|
|
365
|
-
) -> Tuple[str, Dict[str, Any]]:
|
|
366
|
-
"""
|
|
367
|
-
Execute Python code using Google Gemini's code execution feature.
|
|
368
|
-
|
|
369
|
-
Args:
|
|
370
|
-
code_request: Description of the code task to execute
|
|
371
|
-
|
|
372
|
-
Returns:
|
|
373
|
-
Tuple[str, Dict]: (message for model, detailed execution results)
|
|
374
|
-
"""
|
|
375
|
-
tool_instance = GeminiCodeExecutionTool()
|
|
376
|
-
return tool_instance._run(code_request, config)
|
|
1
|
+
"""
|
|
2
|
+
Gemini Code Execution Tool for LangGraph
|
|
3
|
+
使用 Google Gemini API 的 code execution 功能執行 Python 程式碼
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
from typing import ClassVar, Dict, Tuple, Any, Optional
|
|
9
|
+
from langchain_core.tools import BaseTool
|
|
10
|
+
from langchain_core.runnables import RunnableConfig
|
|
11
|
+
from langchain_core.messages import ToolMessage
|
|
12
|
+
from langgraph.config import get_stream_writer
|
|
13
|
+
from langgraph.types import StreamWriter
|
|
14
|
+
|
|
15
|
+
from botrun_flow_lang.constants import LANG_EN, LANG_ZH_TW
|
|
16
|
+
from botrun_flow_lang.utils.botrun_logger import BotrunLogger
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GeminiCodeExecutionTool(BaseTool):
|
|
20
|
+
"""
|
|
21
|
+
使用 Google Gemini API 的 code execution 功能執行 Python 程式碼
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# 類屬性定義
|
|
25
|
+
tool_name: ClassVar[str] = "gemini_code_execution"
|
|
26
|
+
|
|
27
|
+
# 定義多語言描述
|
|
28
|
+
descriptions: ClassVar[Dict[str, str]] = {
|
|
29
|
+
LANG_EN: """
|
|
30
|
+
Execute Python code using Google Gemini's code execution feature.
|
|
31
|
+
This tool allows Gemini to generate and run Python code iteratively until it produces a final output.
|
|
32
|
+
|
|
33
|
+
Capabilities:
|
|
34
|
+
- Mathematical calculations and data analysis
|
|
35
|
+
- Text processing and manipulation
|
|
36
|
+
- Using libraries: NumPy, Pandas, SymPy, and more
|
|
37
|
+
- Iterative code improvement based on execution results
|
|
38
|
+
- Maximum execution time: 30 seconds
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
1. Mathematical calculation:
|
|
42
|
+
User: "Calculate the sum of the first 50 prime numbers"
|
|
43
|
+
gemini_code_execution("Calculate the sum of the first 50 prime numbers")
|
|
44
|
+
Returns: Code and result showing the sum is 5117
|
|
45
|
+
|
|
46
|
+
2. Text processing:
|
|
47
|
+
User: "Count word frequency in a text"
|
|
48
|
+
gemini_code_execution("Count word frequency in this text: 'The quick brown fox jumps over the lazy dog'")
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
code_request: Description of the code task to execute. Be specific about:
|
|
52
|
+
- What calculation or analysis to perform
|
|
53
|
+
- Input data (if any)
|
|
54
|
+
- Expected output format
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
dict: Contains:
|
|
58
|
+
- summary: Brief description of what was done
|
|
59
|
+
- code_blocks: List of executed code blocks
|
|
60
|
+
- results: Execution outputs
|
|
61
|
+
- images: List of generated images (if any)
|
|
62
|
+
- error: Error message (if any)
|
|
63
|
+
""",
|
|
64
|
+
LANG_ZH_TW: """
|
|
65
|
+
使用 Google Gemini 的程式碼執行功能執行 Python 程式碼。
|
|
66
|
+
此工具讓 Gemini 可以生成並執行 Python 程式碼,並根據結果反覆改進直到產生最終輸出。
|
|
67
|
+
|
|
68
|
+
功能特色:
|
|
69
|
+
- 數學計算和資料分析
|
|
70
|
+
- 文字處理和操作
|
|
71
|
+
- 支援函式庫:NumPy、Pandas、SymPy 等
|
|
72
|
+
- 根據執行結果反覆改進程式碼
|
|
73
|
+
- 最長執行時間:30 秒
|
|
74
|
+
|
|
75
|
+
使用範例:
|
|
76
|
+
1. 數學計算:
|
|
77
|
+
使用者:「計算前 50 個質數的總和」
|
|
78
|
+
gemini_code_execution("計算前 50 個質數的總和")
|
|
79
|
+
回傳:程式碼和結果顯示總和為 5117
|
|
80
|
+
|
|
81
|
+
2. 文字處理:
|
|
82
|
+
使用者:「計算文字中的詞頻」
|
|
83
|
+
gemini_code_execution("計算這段文字的詞頻:'The quick brown fox jumps over the lazy dog'")
|
|
84
|
+
|
|
85
|
+
參數:
|
|
86
|
+
code_request: 程式碼任務的描述。請具體說明:
|
|
87
|
+
- 要執行什麼計算或分析
|
|
88
|
+
- 輸入資料(如果有)
|
|
89
|
+
- 期望的輸出格式
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
dict: 包含:
|
|
93
|
+
- summary: 執行內容的簡短描述
|
|
94
|
+
- code_blocks: 執行的程式碼區塊列表
|
|
95
|
+
- results: 執行輸出
|
|
96
|
+
- images: 產生的圖片列表(如果有)
|
|
97
|
+
- error: 錯誤訊息(如果有)
|
|
98
|
+
""",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Pydantic 模型字段
|
|
102
|
+
name: str = "gemini_code_execution"
|
|
103
|
+
description: str = descriptions[LANG_EN]
|
|
104
|
+
lang: str = LANG_EN
|
|
105
|
+
response_format: str = "content_and_artifact" # 支援 artifact 回傳
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def for_language(cls, lang: str = LANG_EN):
|
|
109
|
+
"""創建特定語言版本的工具實例"""
|
|
110
|
+
description = cls.descriptions.get(lang, cls.descriptions.get(LANG_EN))
|
|
111
|
+
return cls(name=cls.tool_name, description=description, lang=lang)
|
|
112
|
+
|
|
113
|
+
def _run(
|
|
114
|
+
self,
|
|
115
|
+
code_request: str,
|
|
116
|
+
config: RunnableConfig = None,
|
|
117
|
+
) -> Tuple[str, Dict[str, Any]]:
|
|
118
|
+
"""
|
|
119
|
+
執行 Gemini code execution 並返回結果
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Tuple[str, Dict]: (給模型的訊息, 詳細執行結果)
|
|
123
|
+
"""
|
|
124
|
+
logger = BotrunLogger()
|
|
125
|
+
logger.info(
|
|
126
|
+
f"gemini_code_execution request",
|
|
127
|
+
code_request=code_request,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# 取得 stream writer 來輸出即時訊息
|
|
131
|
+
try:
|
|
132
|
+
writer = get_stream_writer()
|
|
133
|
+
has_writer = True
|
|
134
|
+
except Exception:
|
|
135
|
+
# 如果不在 LangGraph context 中,writer 會是 None
|
|
136
|
+
writer = None
|
|
137
|
+
has_writer = False
|
|
138
|
+
|
|
139
|
+
# 發送開始訊息
|
|
140
|
+
if has_writer:
|
|
141
|
+
writer(
|
|
142
|
+
{
|
|
143
|
+
"type": "status",
|
|
144
|
+
"message": f"🚀 Starting code execution for: {code_request[:100]}...",
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# 初始化 Gemini client
|
|
150
|
+
from google import genai
|
|
151
|
+
from google.genai import types
|
|
152
|
+
from google.oauth2 import service_account
|
|
153
|
+
|
|
154
|
+
credentials = service_account.Credentials.from_service_account_file(
|
|
155
|
+
os.getenv("GOOGLE_APPLICATION_CREDENTIALS_FOR_FASTAPI"),
|
|
156
|
+
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
client = genai.Client(
|
|
160
|
+
credentials=credentials,
|
|
161
|
+
project="scoop-386004",
|
|
162
|
+
location="us-central1",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# 發送 API 調用狀態
|
|
166
|
+
if has_writer:
|
|
167
|
+
writer(
|
|
168
|
+
{
|
|
169
|
+
"type": "progress",
|
|
170
|
+
"message": "📡 Calling Gemini API with code execution enabled...",
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# 調用 Gemini API 並啟用 code execution
|
|
175
|
+
response = client.models.generate_content(
|
|
176
|
+
model="gemini-2.5-pro",
|
|
177
|
+
contents=code_request,
|
|
178
|
+
config=types.GenerateContentConfig(
|
|
179
|
+
tools=[types.Tool(code_execution=types.ToolCodeExecution())]
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# 發送收到回應的狀態
|
|
184
|
+
if has_writer:
|
|
185
|
+
writer(
|
|
186
|
+
{
|
|
187
|
+
"type": "progress",
|
|
188
|
+
"message": "✅ Received response from Gemini API",
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# 解析回應
|
|
193
|
+
executed_code = []
|
|
194
|
+
execution_results = []
|
|
195
|
+
summary_text = []
|
|
196
|
+
images = []
|
|
197
|
+
|
|
198
|
+
for part in response.candidates[0].content.parts:
|
|
199
|
+
if hasattr(part, "text") and part.text:
|
|
200
|
+
summary_text.append(part.text)
|
|
201
|
+
# 即時輸出 Gemini 的文字說明
|
|
202
|
+
if has_writer:
|
|
203
|
+
writer(
|
|
204
|
+
{
|
|
205
|
+
"type": "text",
|
|
206
|
+
"content": part.text,
|
|
207
|
+
"source": "gemini_explanation",
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if hasattr(part, "executable_code") and part.executable_code:
|
|
212
|
+
code_info = {
|
|
213
|
+
"code": part.executable_code.code,
|
|
214
|
+
"language": getattr(part.executable_code, "language", "PYTHON"),
|
|
215
|
+
}
|
|
216
|
+
executed_code.append(code_info)
|
|
217
|
+
logger.debug(f"Executed code block", code_info=code_info)
|
|
218
|
+
|
|
219
|
+
# 即時輸出程式碼
|
|
220
|
+
if has_writer:
|
|
221
|
+
writer(
|
|
222
|
+
{
|
|
223
|
+
"type": "code",
|
|
224
|
+
"language": code_info["language"],
|
|
225
|
+
"content": code_info["code"],
|
|
226
|
+
"message": f"💻 Executing {code_info['language']} code block #{len(executed_code)}",
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if (
|
|
231
|
+
hasattr(part, "code_execution_result")
|
|
232
|
+
and part.code_execution_result
|
|
233
|
+
):
|
|
234
|
+
result_info = {
|
|
235
|
+
"output": part.code_execution_result.output,
|
|
236
|
+
"outcome": getattr(
|
|
237
|
+
part.code_execution_result, "outcome", "UNKNOWN"
|
|
238
|
+
),
|
|
239
|
+
}
|
|
240
|
+
execution_results.append(result_info)
|
|
241
|
+
logger.debug(f"Execution result", result_info=result_info)
|
|
242
|
+
|
|
243
|
+
# 即時輸出執行結果
|
|
244
|
+
if has_writer:
|
|
245
|
+
status_emoji = (
|
|
246
|
+
"✅" if result_info["outcome"] != "ERROR" else "❌"
|
|
247
|
+
)
|
|
248
|
+
writer(
|
|
249
|
+
{
|
|
250
|
+
"type": "execution_result",
|
|
251
|
+
"outcome": result_info["outcome"],
|
|
252
|
+
"output": result_info["output"],
|
|
253
|
+
"message": f"{status_emoji} Execution result (outcome: {result_info['outcome']})",
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# 檢查是否有圖片輸出(Matplotlib 產生的圖表)
|
|
258
|
+
if hasattr(part, "inline_data") and part.inline_data:
|
|
259
|
+
image_info = {
|
|
260
|
+
"mime_type": part.inline_data.mime_type,
|
|
261
|
+
"data": part.inline_data.data, # Base64 encoded image
|
|
262
|
+
}
|
|
263
|
+
images.append(image_info)
|
|
264
|
+
logger.info(f"Generated image", mime_type=image_info["mime_type"])
|
|
265
|
+
|
|
266
|
+
# 即時輸出圖片生成通知
|
|
267
|
+
if has_writer:
|
|
268
|
+
writer(
|
|
269
|
+
{
|
|
270
|
+
"type": "image_generated",
|
|
271
|
+
"mime_type": image_info["mime_type"],
|
|
272
|
+
"message": f"📊 Generated visualization ({image_info['mime_type']})",
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# 準備 artifact(詳細結果)
|
|
277
|
+
artifact = {
|
|
278
|
+
"executed_code": executed_code,
|
|
279
|
+
"execution_results": execution_results,
|
|
280
|
+
"images": images,
|
|
281
|
+
"full_response": str(response), # 保存完整回應以供調試
|
|
282
|
+
"has_visualization": len(images) > 0,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# 準備給模型的簡潔訊息
|
|
286
|
+
if summary_text:
|
|
287
|
+
content = " ".join(summary_text)
|
|
288
|
+
else:
|
|
289
|
+
content = "Code execution completed successfully."
|
|
290
|
+
|
|
291
|
+
if images:
|
|
292
|
+
content += f" Generated {len(images)} visualization(s)."
|
|
293
|
+
|
|
294
|
+
# 發送完成狀態
|
|
295
|
+
if has_writer:
|
|
296
|
+
writer(
|
|
297
|
+
{
|
|
298
|
+
"type": "completion",
|
|
299
|
+
"message": f"🎉 Code execution completed successfully!",
|
|
300
|
+
"stats": {
|
|
301
|
+
"code_blocks": len(executed_code),
|
|
302
|
+
"results": len(execution_results),
|
|
303
|
+
"images": len(images),
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
logger.info(
|
|
309
|
+
"gemini_code_execution completed",
|
|
310
|
+
num_code_blocks=len(executed_code),
|
|
311
|
+
num_results=len(execution_results),
|
|
312
|
+
num_images=len(images),
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# 回傳 tuple 格式以支援 artifact
|
|
316
|
+
return content, artifact
|
|
317
|
+
|
|
318
|
+
except Exception as e:
|
|
319
|
+
error_msg = f"Code execution failed: {str(e)}"
|
|
320
|
+
logger.error(error_msg, error=str(e), exc_info=True)
|
|
321
|
+
|
|
322
|
+
# 發送錯誤訊息
|
|
323
|
+
if has_writer:
|
|
324
|
+
writer(
|
|
325
|
+
{
|
|
326
|
+
"type": "error",
|
|
327
|
+
"message": f"❌ {error_msg}",
|
|
328
|
+
"error_type": type(e).__name__,
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
error_artifact = {
|
|
333
|
+
"error": str(e),
|
|
334
|
+
"error_type": type(e).__name__,
|
|
335
|
+
"executed_code": [],
|
|
336
|
+
"execution_results": [],
|
|
337
|
+
"images": [],
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return error_msg, error_artifact
|
|
341
|
+
|
|
342
|
+
async def _arun(
|
|
343
|
+
self,
|
|
344
|
+
code_request: str,
|
|
345
|
+
config: RunnableConfig = None,
|
|
346
|
+
) -> Tuple[str, Dict[str, Any]]:
|
|
347
|
+
"""
|
|
348
|
+
異步版本的 _run 方法
|
|
349
|
+
目前直接調用同步版本,未來可以改為真正的異步實現
|
|
350
|
+
"""
|
|
351
|
+
# 注意:在 Python < 3.11 中,異步函數可能無法使用 get_stream_writer()
|
|
352
|
+
# 需要將 writer 作為參數傳入
|
|
353
|
+
# 目前這裡保持簡單,直接調用同步版本
|
|
354
|
+
return self._run(code_request, config)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# 建立一個便利的函數裝飾器版本,供簡單使用
|
|
358
|
+
from langchain_core.tools import tool
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@tool(response_format="content_and_artifact")
|
|
362
|
+
def gemini_code_execution(
|
|
363
|
+
code_request: str,
|
|
364
|
+
config: RunnableConfig = None,
|
|
365
|
+
) -> Tuple[str, Dict[str, Any]]:
|
|
366
|
+
"""
|
|
367
|
+
Execute Python code using Google Gemini's code execution feature.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
code_request: Description of the code task to execute
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Tuple[str, Dict]: (message for model, detailed execution results)
|
|
374
|
+
"""
|
|
375
|
+
tool_instance = GeminiCodeExecutionTool()
|
|
376
|
+
return tool_instance._run(code_request, config)
|