botrun-flow-lang 5.12.263__py3-none-any.whl → 6.2.21__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 (89) hide show
  1. botrun_flow_lang/api/auth_api.py +39 -39
  2. botrun_flow_lang/api/auth_utils.py +183 -183
  3. botrun_flow_lang/api/botrun_back_api.py +65 -65
  4. botrun_flow_lang/api/flow_api.py +3 -3
  5. botrun_flow_lang/api/hatch_api.py +508 -508
  6. botrun_flow_lang/api/langgraph_api.py +816 -811
  7. botrun_flow_lang/api/langgraph_constants.py +11 -0
  8. botrun_flow_lang/api/line_bot_api.py +1484 -1484
  9. botrun_flow_lang/api/model_api.py +300 -300
  10. botrun_flow_lang/api/rate_limit_api.py +32 -32
  11. botrun_flow_lang/api/routes.py +79 -79
  12. botrun_flow_lang/api/search_api.py +53 -53
  13. botrun_flow_lang/api/storage_api.py +395 -395
  14. botrun_flow_lang/api/subsidy_api.py +290 -290
  15. botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
  16. botrun_flow_lang/api/user_setting_api.py +70 -70
  17. botrun_flow_lang/api/version_api.py +31 -31
  18. botrun_flow_lang/api/youtube_api.py +26 -26
  19. botrun_flow_lang/constants.py +13 -13
  20. botrun_flow_lang/langgraph_agents/agents/agent_runner.py +178 -178
  21. botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
  22. botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
  23. botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
  24. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gemini_subsidy_graph.py +460 -460
  25. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
  26. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
  27. botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +730 -723
  28. botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
  29. botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
  30. botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
  31. botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
  32. botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
  33. botrun_flow_lang/langgraph_agents/agents/util/img_util.py +336 -294
  34. botrun_flow_lang/langgraph_agents/agents/util/local_files.py +419 -419
  35. botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
  36. botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
  37. botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +562 -486
  38. botrun_flow_lang/langgraph_agents/agents/util/pdf_cache.py +250 -250
  39. botrun_flow_lang/langgraph_agents/agents/util/pdf_processor.py +204 -204
  40. botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
  41. botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
  42. botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
  43. botrun_flow_lang/langgraph_agents/agents/util/usage_metadata.py +34 -0
  44. botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
  45. botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
  46. botrun_flow_lang/llm_agent/llm_agent.py +19 -19
  47. botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
  48. botrun_flow_lang/log/.gitignore +2 -2
  49. botrun_flow_lang/main.py +61 -61
  50. botrun_flow_lang/main_fast.py +51 -51
  51. botrun_flow_lang/mcp_server/__init__.py +10 -10
  52. botrun_flow_lang/mcp_server/default_mcp.py +854 -744
  53. botrun_flow_lang/models/nodes/utils.py +205 -205
  54. botrun_flow_lang/models/token_usage.py +34 -34
  55. botrun_flow_lang/requirements.txt +21 -21
  56. botrun_flow_lang/services/base/firestore_base.py +30 -30
  57. botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
  58. botrun_flow_lang/services/hatch/hatch_fs_store.py +419 -419
  59. botrun_flow_lang/services/storage/storage_cs_store.py +206 -206
  60. botrun_flow_lang/services/storage/storage_factory.py +12 -12
  61. botrun_flow_lang/services/storage/storage_store.py +65 -65
  62. botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
  63. botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
  64. botrun_flow_lang/static/docs/tools/index.html +926 -926
  65. botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
  66. botrun_flow_lang/tests/api_stress_test.py +357 -357
  67. botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
  68. botrun_flow_lang/tests/test_botrun_app.py +46 -46
  69. botrun_flow_lang/tests/test_html_util.py +31 -31
  70. botrun_flow_lang/tests/test_img_analyzer.py +190 -190
  71. botrun_flow_lang/tests/test_img_util.py +39 -39
  72. botrun_flow_lang/tests/test_local_files.py +114 -114
  73. botrun_flow_lang/tests/test_mermaid_util.py +103 -103
  74. botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
  75. botrun_flow_lang/tests/test_plotly_util.py +151 -151
  76. botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
  77. botrun_flow_lang/tools/generate_docs.py +133 -133
  78. botrun_flow_lang/tools/templates/tools.html +153 -153
  79. botrun_flow_lang/utils/__init__.py +7 -7
  80. botrun_flow_lang/utils/botrun_logger.py +344 -344
  81. botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
  82. botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
  83. botrun_flow_lang/utils/google_drive_utils.py +654 -654
  84. botrun_flow_lang/utils/langchain_utils.py +324 -324
  85. botrun_flow_lang/utils/yaml_utils.py +9 -9
  86. {botrun_flow_lang-5.12.263.dist-info → botrun_flow_lang-6.2.21.dist-info}/METADATA +6 -6
  87. botrun_flow_lang-6.2.21.dist-info/RECORD +104 -0
  88. botrun_flow_lang-5.12.263.dist-info/RECORD +0 -102
  89. {botrun_flow_lang-5.12.263.dist-info → botrun_flow_lang-6.2.21.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)