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.
Files changed (84) 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 +481 -481
  6. botrun_flow_lang/api/langgraph_api.py +796 -796
  7. botrun_flow_lang/api/line_bot_api.py +1357 -1357
  8. botrun_flow_lang/api/model_api.py +300 -300
  9. botrun_flow_lang/api/rate_limit_api.py +32 -32
  10. botrun_flow_lang/api/routes.py +79 -79
  11. botrun_flow_lang/api/search_api.py +53 -53
  12. botrun_flow_lang/api/storage_api.py +316 -316
  13. botrun_flow_lang/api/subsidy_api.py +290 -290
  14. botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
  15. botrun_flow_lang/api/user_setting_api.py +70 -70
  16. botrun_flow_lang/api/version_api.py +31 -31
  17. botrun_flow_lang/api/youtube_api.py +26 -26
  18. botrun_flow_lang/constants.py +13 -13
  19. botrun_flow_lang/langgraph_agents/agents/agent_runner.py +174 -174
  20. botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
  21. botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
  22. botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
  23. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
  24. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
  25. botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +548 -542
  26. botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
  27. botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
  28. botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
  29. botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
  30. botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
  31. botrun_flow_lang/langgraph_agents/agents/util/img_util.py +294 -294
  32. botrun_flow_lang/langgraph_agents/agents/util/local_files.py +345 -345
  33. botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
  34. botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
  35. botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +160 -160
  36. botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
  37. botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
  38. botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
  39. botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
  40. botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
  41. botrun_flow_lang/llm_agent/llm_agent.py +19 -19
  42. botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
  43. botrun_flow_lang/log/.gitignore +2 -2
  44. botrun_flow_lang/main.py +61 -61
  45. botrun_flow_lang/main_fast.py +51 -51
  46. botrun_flow_lang/mcp_server/__init__.py +10 -10
  47. botrun_flow_lang/mcp_server/default_mcp.py +711 -711
  48. botrun_flow_lang/models/nodes/utils.py +205 -205
  49. botrun_flow_lang/models/token_usage.py +34 -34
  50. botrun_flow_lang/requirements.txt +21 -21
  51. botrun_flow_lang/services/base/firestore_base.py +30 -30
  52. botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
  53. botrun_flow_lang/services/hatch/hatch_fs_store.py +372 -372
  54. botrun_flow_lang/services/storage/storage_cs_store.py +202 -202
  55. botrun_flow_lang/services/storage/storage_factory.py +12 -12
  56. botrun_flow_lang/services/storage/storage_store.py +65 -65
  57. botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
  58. botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
  59. botrun_flow_lang/static/docs/tools/index.html +926 -926
  60. botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
  61. botrun_flow_lang/tests/api_stress_test.py +357 -357
  62. botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
  63. botrun_flow_lang/tests/test_botrun_app.py +46 -46
  64. botrun_flow_lang/tests/test_html_util.py +31 -31
  65. botrun_flow_lang/tests/test_img_analyzer.py +190 -190
  66. botrun_flow_lang/tests/test_img_util.py +39 -39
  67. botrun_flow_lang/tests/test_local_files.py +114 -114
  68. botrun_flow_lang/tests/test_mermaid_util.py +103 -103
  69. botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
  70. botrun_flow_lang/tests/test_plotly_util.py +151 -151
  71. botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
  72. botrun_flow_lang/tools/generate_docs.py +133 -133
  73. botrun_flow_lang/tools/templates/tools.html +153 -153
  74. botrun_flow_lang/utils/__init__.py +7 -7
  75. botrun_flow_lang/utils/botrun_logger.py +344 -344
  76. botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
  77. botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
  78. botrun_flow_lang/utils/google_drive_utils.py +654 -654
  79. botrun_flow_lang/utils/langchain_utils.py +324 -324
  80. botrun_flow_lang/utils/yaml_utils.py +9 -9
  81. {botrun_flow_lang-5.9.301.dist-info → botrun_flow_lang-5.10.82.dist-info}/METADATA +2 -2
  82. botrun_flow_lang-5.10.82.dist-info/RECORD +99 -0
  83. botrun_flow_lang-5.9.301.dist-info/RECORD +0 -99
  84. {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)