botrun-flow-lang 5.12.262__py3-none-any.whl → 5.12.264__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 (87) 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 +811 -811
  7. botrun_flow_lang/api/line_bot_api.py +1484 -1484
  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 +395 -395
  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 +178 -178
  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/gemini_subsidy_graph.py +460 -460
  24. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
  25. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
  26. botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +723 -723
  27. botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
  28. botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
  29. botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
  30. botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
  31. botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
  32. botrun_flow_lang/langgraph_agents/agents/util/img_util.py +294 -294
  33. botrun_flow_lang/langgraph_agents/agents/util/local_files.py +419 -419
  34. botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
  35. botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
  36. botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +486 -486
  37. botrun_flow_lang/langgraph_agents/agents/util/pdf_cache.py +250 -250
  38. botrun_flow_lang/langgraph_agents/agents/util/pdf_processor.py +204 -204
  39. botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
  40. botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
  41. botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
  42. botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
  43. botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
  44. botrun_flow_lang/llm_agent/llm_agent.py +19 -19
  45. botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
  46. botrun_flow_lang/log/.gitignore +2 -2
  47. botrun_flow_lang/main.py +61 -61
  48. botrun_flow_lang/main_fast.py +51 -51
  49. botrun_flow_lang/mcp_server/__init__.py +10 -10
  50. botrun_flow_lang/mcp_server/default_mcp.py +744 -744
  51. botrun_flow_lang/models/nodes/utils.py +205 -205
  52. botrun_flow_lang/models/token_usage.py +34 -34
  53. botrun_flow_lang/requirements.txt +21 -21
  54. botrun_flow_lang/services/base/firestore_base.py +30 -30
  55. botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
  56. botrun_flow_lang/services/hatch/hatch_fs_store.py +419 -387
  57. botrun_flow_lang/services/storage/storage_cs_store.py +206 -206
  58. botrun_flow_lang/services/storage/storage_factory.py +12 -12
  59. botrun_flow_lang/services/storage/storage_store.py +65 -65
  60. botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
  61. botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
  62. botrun_flow_lang/static/docs/tools/index.html +926 -926
  63. botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
  64. botrun_flow_lang/tests/api_stress_test.py +357 -357
  65. botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
  66. botrun_flow_lang/tests/test_botrun_app.py +46 -46
  67. botrun_flow_lang/tests/test_html_util.py +31 -31
  68. botrun_flow_lang/tests/test_img_analyzer.py +190 -190
  69. botrun_flow_lang/tests/test_img_util.py +39 -39
  70. botrun_flow_lang/tests/test_local_files.py +114 -114
  71. botrun_flow_lang/tests/test_mermaid_util.py +103 -103
  72. botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
  73. botrun_flow_lang/tests/test_plotly_util.py +151 -151
  74. botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
  75. botrun_flow_lang/tools/generate_docs.py +133 -133
  76. botrun_flow_lang/tools/templates/tools.html +153 -153
  77. botrun_flow_lang/utils/__init__.py +7 -7
  78. botrun_flow_lang/utils/botrun_logger.py +344 -344
  79. botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
  80. botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
  81. botrun_flow_lang/utils/google_drive_utils.py +654 -654
  82. botrun_flow_lang/utils/langchain_utils.py +324 -324
  83. botrun_flow_lang/utils/yaml_utils.py +9 -9
  84. {botrun_flow_lang-5.12.262.dist-info → botrun_flow_lang-5.12.264.dist-info}/METADATA +1 -1
  85. botrun_flow_lang-5.12.264.dist-info/RECORD +102 -0
  86. botrun_flow_lang-5.12.262.dist-info/RECORD +0 -102
  87. {botrun_flow_lang-5.12.262.dist-info → botrun_flow_lang-5.12.264.dist-info}/WHEEL +0 -0
@@ -1,744 +1,744 @@
1
- import os
2
- import asyncio
3
- import json
4
- import pytz
5
- from datetime import datetime
6
- from typing import Dict, Any, Optional
7
-
8
- # Import for web_search
9
- from botrun_flow_lang.langgraph_agents.agents.util.perplexity_search import (
10
- respond_with_perplexity_search,
11
- )
12
-
13
- from mcp.server.fastmcp import FastMCP
14
- from langchain_core.runnables import RunnableConfig
15
-
16
- # Import necessary dependencies
17
- from botrun_flow_lang.models.nodes.utils import scrape_single_url
18
- from botrun_flow_lang.langgraph_agents.agents.util.pdf_analyzer import (
19
- analyze_pdf_async,
20
- )
21
- from botrun_flow_lang.langgraph_agents.agents.util.img_util import analyze_imgs
22
- from botrun_flow_lang.langgraph_agents.agents.util.local_files import (
23
- upload_and_get_tmp_public_url,
24
- )
25
- from botrun_flow_lang.langgraph_agents.agents.util.html_util import generate_html_file
26
- from botrun_flow_lang.langgraph_agents.agents.util.plotly_util import (
27
- generate_plotly_files,
28
- )
29
- from botrun_flow_lang.langgraph_agents.agents.util.mermaid_util import (
30
- generate_mermaid_files,
31
- )
32
- from botrun_flow_lang.utils.clients.rate_limit_client import RateLimitClient
33
- from botrun_flow_lang.utils.botrun_logger import get_default_botrun_logger
34
-
35
- # Import for generate_image
36
- from langchain_community.utilities.dalle_image_generator import DallEAPIWrapper
37
- from langchain_community.callbacks import get_openai_callback
38
-
39
- # Initialize MCP server
40
- mcp = FastMCP(name="BotrunFlowLangDefaultMCP", stateless_http=True)
41
-
42
- # Initialize logger - reuse the default instance
43
- logger = get_default_botrun_logger()
44
-
45
-
46
- # Exception class for rate limit errors
47
- class BotrunRateLimitException(Exception):
48
- """Exception that should be displayed directly to the user."""
49
-
50
- def __init__(self, message):
51
- self.message = f"[Please tell user error] {message}"
52
- super().__init__(self.message)
53
-
54
-
55
- @mcp.tool()
56
- async def scrape(url: str) -> dict:
57
- """
58
- Scrape a web page to extract its content.
59
-
60
- Args:
61
- url: The URL to scrape
62
-
63
- Returns:
64
- dict: A dictionary containing the scraped content or error message
65
- """
66
- try:
67
- logger.info(f"scrape {url}")
68
- return await scrape_single_url(url)
69
- except Exception as e:
70
- logger.error(f"scrape {url} error: {e}", error=str(e), exc_info=True)
71
- return {"url": url, "status": "error", "error": str(e)}
72
-
73
-
74
- @mcp.tool()
75
- async def chat_with_pdf(
76
- pdf_url: str, user_input: str, botrun_flow_lang_url: str, user_id: str
77
- ) -> str:
78
- """
79
- Analyze a PDF file and answer questions about its content.
80
-
81
- Supports intelligent processing based on file size:
82
- - Small files (< 5MB): Direct multimodal analysis
83
- - Large files (>= 5MB): Compress -> Split -> Parallel multimodal Q&A -> Merge results
84
-
85
- Args:
86
- pdf_url: The URL to the PDF file (can be generated using generate_tmp_public_url for local files)
87
- user_input: The user's question or instruction about the PDF content
88
- botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
89
- user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
90
-
91
- Returns:
92
- str: Analysis result or Plotly-compatible data structure if visualization is needed
93
- """
94
- logger.info(f"chat_with_pdf pdf_url: {pdf_url} user_input: {user_input}")
95
-
96
- # Convert local file path to public URL if needed
97
- if not pdf_url.startswith("http"):
98
- pdf_url = upload_and_get_tmp_public_url(pdf_url, botrun_flow_lang_url, user_id)
99
-
100
- return await analyze_pdf_async(pdf_url, user_input)
101
-
102
-
103
- @mcp.tool()
104
- async def chat_with_imgs(
105
- img_urls: list[str],
106
- user_input: str,
107
- botrun_flow_lang_url: str,
108
- user_id: str,
109
- ) -> str:
110
- """
111
- Analyze multiple images and answer questions about their content.
112
-
113
- Args:
114
- img_urls: List of URLs to the image files (can be generated using generate_tmp_public_url for local files)
115
- user_input: Question or instruction about the image content(s)
116
- botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
117
- user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
118
-
119
- Returns:
120
- str: Analysis result or Plotly-compatible data structure if visualization is needed
121
- """
122
- logger.info(f"chat_with_imgs img_urls: {img_urls} user_input: {user_input}")
123
-
124
- # Convert local file paths to public URLs if needed
125
- new_img_urls = []
126
- for img_url in img_urls:
127
- if not img_url.startswith("http"):
128
- img_url = upload_and_get_tmp_public_url(
129
- img_url, botrun_flow_lang_url, user_id
130
- )
131
- new_img_urls.append(img_url)
132
-
133
- return analyze_imgs(new_img_urls, user_input)
134
-
135
-
136
- @mcp.tool()
137
- async def generate_image(
138
- user_input: str, user_id: str = "", botrun_flow_lang_url: str = ""
139
- ) -> str:
140
- """
141
- Generate high-quality images using DALL-E 3 and store permanently in GCS.
142
-
143
- When using generate_image tool, you must include the image URL in your response.
144
- You MUST respond using this format (from @begin img to @end, including the image URL):
145
- @begin img("{image_url}") @end
146
-
147
- Capabilities:
148
- - Creates photorealistic images and art
149
- - Handles complex scenes and compositions
150
- - Maintains consistent styles
151
- - Follows detailed prompts with high accuracy
152
- - Supports various artistic styles and mediums
153
-
154
- Best practices for prompts:
155
- - Be specific about style, mood, lighting, and composition
156
- - Include details about perspective and setting
157
- - Specify artistic medium if desired (e.g., "oil painting", "digital art")
158
- - Mention color schemes or specific colors
159
- - Describe the atmosphere or emotion you want to convey
160
-
161
- Limitations:
162
- - Cannot generate images of public figures or celebrities
163
- - Avoids harmful, violent, or adult content
164
- - May have inconsistencies with hands, faces, or text
165
- - Cannot generate exact copies of existing artworks or brands
166
- - Limited to single image generation per request
167
- - Subject to daily usage limits
168
-
169
- Args:
170
- user_input: Detailed description of the image you want to generate.
171
- Be specific about style, content, and composition.
172
- user_id: REQUIRED - User ID for rate limit and file storage (LLM can get this from system prompt)
173
- botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
174
-
175
- Returns:
176
- str: Permanent URL to the generated image stored in GCS, or error message if generation fails
177
- """
178
- try:
179
- logger.info(f"generate_image user_input: {user_input}")
180
-
181
- # 驗證必要參數
182
- if not user_id:
183
- logger.error("User ID not available")
184
- return "User ID not available"
185
- if not botrun_flow_lang_url:
186
- logger.error("botrun_flow_lang_url not available")
187
- return "botrun_flow_lang_url not available"
188
-
189
- # Check rate limit before generating image
190
- rate_limit_client = RateLimitClient()
191
- rate_limit_info = await rate_limit_client.get_rate_limit(user_id)
192
-
193
- # Check if user can generate an image
194
- drawing_info = rate_limit_info.get("drawing", {})
195
- can_use = drawing_info.get("can_use", False)
196
-
197
- if not can_use:
198
- daily_limit = drawing_info.get("daily_limit", 0)
199
- current_usage = drawing_info.get("current_usage", 0)
200
- logger.error(
201
- f"User {user_id} has reached daily limit of {daily_limit} image generations. "
202
- f"Current usage: {current_usage}. Please try again tomorrow."
203
- )
204
- return f"[Please tell user error] You have reached your daily limit of {daily_limit} image generations. " \
205
- f"Current usage: {current_usage}. Please try again tomorrow."
206
- # raise BotrunRateLimitException(
207
- # f"You have reached your daily limit of {daily_limit} image generations. "
208
- # f"Current usage: {current_usage}. Please try again tomorrow."
209
- # )
210
-
211
- # 2. 使用 DALL-E 生成圖片
212
- dalle_wrapper = DallEAPIWrapper(
213
- api_key=os.getenv("OPENAI_API_KEY"), model="dall-e-3"
214
- )
215
-
216
- # Generate image with token usage tracking
217
- with get_openai_callback() as cb:
218
- temp_image_url = dalle_wrapper.run(user_input)
219
- logger.info(
220
- f"DALL-E generated temporary URL: {temp_image_url}, "
221
- f"prompt tokens: {cb.prompt_tokens}, "
222
- f"completion tokens: {cb.completion_tokens}"
223
- )
224
-
225
- # 3. 下載並上傳到 GCS,取得永久 URL
226
- from botrun_flow_lang.langgraph_agents.agents.util.local_files import (
227
- upload_image_and_get_public_url,
228
- )
229
-
230
- try:
231
- permanent_url = await upload_image_and_get_public_url(
232
- temp_image_url, botrun_flow_lang_url, user_id
233
- )
234
- logger.info(f"Image uploaded to GCS: {permanent_url}")
235
-
236
- # 4. 更新使用計數
237
- await rate_limit_client.update_drawing_usage(user_id)
238
-
239
- return permanent_url
240
- except Exception as upload_error:
241
- logger.error(
242
- f"Failed to upload to GCS, returning temporary URL: {upload_error}"
243
- )
244
- # Fallback: 回傳臨時 URL
245
- await rate_limit_client.update_drawing_usage(user_id)
246
- return temp_image_url
247
-
248
- except Exception as e:
249
- logger.error(f"generate_image error: {e}", error=str(e), exc_info=True)
250
-
251
- # Check if this is a user-visible exception
252
- if str(e).startswith("[Please tell user error]"):
253
- return str(e) # Return the error message as is
254
- return f"Error: {e}"
255
-
256
-
257
- @mcp.tool()
258
- async def generate_tmp_public_url(
259
- file_path: str, botrun_flow_lang_url: str, user_id: str
260
- ) -> str:
261
- """
262
- Create a temporary public URL for a local file.
263
-
264
- Args:
265
- file_path: The path to the local file you want to make publicly accessible
266
- botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
267
- user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
268
-
269
- Returns:
270
- str: A public URL that can be used to access the file for 7 days
271
-
272
- Raises:
273
- FileNotFoundError: If the specified file does not exist
274
- """
275
- logger.info(f"generate_tmp_public_url file_path: {file_path}")
276
-
277
- if not os.path.exists(file_path):
278
- return f"File not found: {file_path}"
279
- # raise FileNotFoundError(f"File not found: {file_path}")
280
-
281
- return upload_and_get_tmp_public_url(file_path, botrun_flow_lang_url, user_id)
282
-
283
-
284
- @mcp.tool()
285
- async def create_html_page(
286
- html_content: str,
287
- title: str,
288
- botrun_flow_lang_url: str,
289
- user_id: str,
290
- ) -> str:
291
- """
292
- Create a custom HTML page and return its URL.
293
-
294
- This tool supports complete HTML documents, including JavaScript and CSS, which can be used to create
295
- complex interactive pages.
296
-
297
- Prioritize using the following frameworks for writing HTML:
298
- - DataTables for tables (include in header: <link rel="stylesheet" href="https://cdn.datatables.net/2.3.3/css/dataTables.dataTables.css" /> and <script src="https://cdn.datatables.net/2.3.3/js/dataTables.js"></script>)
299
- - Alpine.js for interactivity (include in header: <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>)
300
- - Tailwind CSS for styling (include in header: <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>)
301
- - daisyUI for UI components (include in header: <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />)
302
- - Chart.js for charts (include in header: <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>)
303
- - Animate.css for animations (include in header: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />)
304
-
305
- Input Options:
306
- You can pass either:
307
- 1. A complete HTML document with doctype, html, head, and body tags
308
- 2. An HTML fragment that will be automatically wrapped in a basic HTML structure
309
-
310
- Args:
311
- html_content: Complete HTML document or fragment. Can include JavaScript, CSS, and other elements.
312
- title: Optional title for the HTML page
313
- botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
314
- user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
315
-
316
- Returns:
317
- str: URL for the HTML page. This URL should be provided to the user,
318
- as they will need to access it to view the content in their web browser.
319
- """
320
- try:
321
- logger.info(f"create_html_page html_content: {html_content} title: {title}")
322
-
323
- html_url = await generate_html_file(
324
- html_content, botrun_flow_lang_url, user_id, title
325
- )
326
-
327
- logger.info(f"create_html_page generated============> {html_url}")
328
- return html_url
329
-
330
- except Exception as e:
331
- logger.error(f"create_html_page error: {e}", error=str(e), exc_info=True)
332
- return f"Error creating HTML page URL: {str(e)}"
333
-
334
-
335
- @mcp.tool()
336
- async def create_plotly_chart(
337
- figure_data: str | dict,
338
- title: str,
339
- botrun_flow_lang_url: str,
340
- user_id: str,
341
- ) -> str:
342
- """
343
- Create an interactive Plotly visualization and return its URL.
344
- This URL should be provided to the user,
345
- as they will need to access it to view the interactive chart in their web browser.
346
-
347
- Scenarios for using create_plotly_chart:
348
- - Need to create data visualizations and charts
349
- - Need to show data trends (line charts)
350
- - Need to compare values (bar charts, pie charts)
351
- - Need to show distributions (scatter plots, heat maps)
352
- - Need to display time series data (timeline charts)
353
- - Need to show geographic information (maps)
354
- - Need to perform multidimensional data analysis (3D charts, bubble charts)
355
- - Need to show statistical distributions (box plots)
356
- - Need to show cumulative trends (area charts)
357
- - Need interactive data exploration
358
-
359
- Integration with Other Tools:
360
- This function can be used in conjunction with chat_with_imgs and chat_with_pdf when they return data
361
- suitable for visualization. When those tools detect a need for visualization, they will return a JSON string
362
- with a "__plotly_data__" key, which can be directly passed to this function.
363
-
364
- Example workflow:
365
- 1. User asks to analyze and visualize data from images/PDFs
366
- 2. chat_with_imgs or chat_with_pdf returns JSON string with "__plotly_data__" key
367
- 3. Pass that string to this function to get an interactive visualization URL
368
-
369
- Supported Chart Types:
370
- - Line charts: For showing trends and time series data
371
- - Bar charts: For comparing values across categories
372
- - Pie charts: For showing proportions of a whole
373
- - Scatter plots: For showing relationships between variables
374
- - Heat maps: For showing patterns in matrix data
375
- - Box plots: For showing statistical distributions
376
- - Geographic maps: For showing spatial data
377
- - 3D plots: For showing three-dimensional data
378
- - Bubble charts: For showing three variables in 2D
379
- - Area charts: For showing cumulative totals over time
380
-
381
- The figure_data can be either:
382
- 1. A JSON string containing plotly figure specifications with 'data' and 'layout'
383
- 2. A dictionary object with plotly figure specifications
384
-
385
- Example JSON string:
386
- '{"data": [{"type": "scatter", "x": [1, 2, 3, 4], "y": [10, 15, 13, 17]}], "layout": {"title": "My Plot"}}'
387
-
388
- Example dictionary:
389
- {
390
- 'data': [{
391
- 'type': 'scatter',
392
- 'x': [1, 2, 3, 4],
393
- 'y': [10, 15, 13, 17]
394
- }],
395
- 'layout': {
396
- 'title': 'My Plot'
397
- }
398
- }
399
-
400
- Args:
401
- figure_data: JSON string OR dictionary containing plotly figure specifications or output from chat_with_imgs/chat_with_pdf.
402
- String inputs will be parsed using json.loads(), dictionary inputs will be used directly.
403
- title: Optional title for the plot.
404
- botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
405
- user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
406
-
407
- Returns:
408
- str: URL for the interactive HTML visualization. This URL should be provided to the user,
409
- as they will need to access it to view the interactive chart in their web browser.
410
- """
411
- try:
412
- logger.info(f"create_plotly_chart figure_data: {figure_data} title: {title}")
413
-
414
- # Handle both string and dict inputs
415
- if isinstance(figure_data, str):
416
- figure_dict = json.loads(figure_data)
417
- else:
418
- figure_dict = figure_data
419
-
420
- # If the input is from chat_with_imgs or chat_with_pdf, extract the plotly data
421
- if "__plotly_data__" in figure_dict:
422
- figure_dict = figure_dict["__plotly_data__"]
423
-
424
- html_url = await generate_plotly_files(
425
- figure_dict,
426
- botrun_flow_lang_url,
427
- user_id,
428
- title,
429
- )
430
-
431
- logger.info(f"create_plotly_chart generated============> {html_url}")
432
- return html_url
433
-
434
- except json.JSONDecodeError as e:
435
- logger.error(
436
- f"create_plotly_chart JSON parsing error: {e}", error=str(e), exc_info=True
437
- )
438
- return f"Error parsing JSON figure_data: {str(e)}. Please ensure figure_data is a valid JSON string or dictionary."
439
- except KeyError as e:
440
- logger.error(
441
- f"create_plotly_chart missing key error: {e}", error=str(e), exc_info=True
442
- )
443
- return f"Error: Missing required key in figure data: {str(e)}. Please ensure figure_data contains 'data' and 'layout' keys."
444
- except Exception as e:
445
- logger.error(f"create_plotly_chart error: {e}", error=str(e), exc_info=True)
446
- return f"Error creating visualization URL: {str(e)}"
447
-
448
-
449
- @mcp.tool()
450
- async def create_mermaid_diagram(
451
- mermaid_data: str,
452
- title: str,
453
- botrun_flow_lang_url: str,
454
- user_id: str,
455
- ) -> str:
456
- """
457
- Create an interactive Mermaid diagram visualization and return its URL.
458
- This URL should be provided to the user,
459
- as they will need to access it to view the interactive diagram in their web browser.
460
-
461
- Scenarios for using create_mermaid_diagram:
462
- - Need to visualize flowcharts, architecture diagrams, or relationship diagrams
463
- - Need to show system architecture (flowchart)
464
- - Need to explain operational processes (flowchart)
465
- - Need to show sequence interactions (sequence diagram)
466
- - Need to show state transitions (state diagram)
467
- - Need to show class relationships (class diagram)
468
- - Need to show entity relationships (ER diagram)
469
- - Need to show project timelines (gantt chart)
470
- - Need to show user journeys (journey)
471
- - Need to show requirement relationships (requirement diagram)
472
- - Need to show resource allocation (pie chart)
473
-
474
- Supported Diagram Types:
475
- 1. Flowcharts (graph TD/LR):
476
- - System architectures
477
- - Process flows
478
- - Decision trees
479
- - Data flows
480
-
481
- 2. Sequence Diagrams (sequenceDiagram):
482
- - API interactions
483
- - System communications
484
- - User interactions
485
- - Message flows
486
-
487
- 3. Class Diagrams (classDiagram):
488
- - Software architecture
489
- - Object relationships
490
- - System components
491
- - Code structure
492
-
493
- 4. State Diagrams (stateDiagram-v2):
494
- - System states
495
- - Workflow states
496
- - Process states
497
- - State transitions
498
-
499
- 5. Entity Relationship Diagrams (erDiagram):
500
- - Database schemas
501
- - Data relationships
502
- - System entities
503
- - Data models
504
-
505
- 6. User Journey Diagrams (journey):
506
- - User experiences
507
- - Customer flows
508
- - Process steps
509
- - Task sequences
510
-
511
- 7. Gantt Charts (gantt):
512
- - Project timelines
513
- - Task schedules
514
- - Resource allocation
515
- - Milestone tracking
516
-
517
- 8. Pie Charts (pie):
518
- - Data distribution
519
- - Resource allocation
520
- - Market share
521
- - Component breakdown
522
-
523
- 9. Requirement Diagrams (requirementDiagram):
524
- - System requirements
525
- - Dependencies
526
- - Specifications
527
- - Constraints
528
-
529
- Example Mermaid syntax for a simple flowchart:
530
- ```
531
- graph TD
532
- A[Start] --> B{Data Available?}
533
- B -->|Yes| C[Process Data]
534
- B -->|No| D[Get Data]
535
- C --> E[End]
536
- D --> B
537
- ```
538
-
539
- Args:
540
- mermaid_data: String containing the Mermaid diagram definition
541
- title: Optional title for the diagram
542
- botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
543
- user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
544
-
545
- Returns:
546
- str: URL for the interactive HTML visualization. This URL should be provided to the user,
547
- as they will need to access it to view the interactive diagram in their web browser.
548
- """
549
- try:
550
- logger.info(
551
- f"create_mermaid_diagram mermaid_data: {mermaid_data} title: {title}"
552
- )
553
-
554
- html_url = await generate_mermaid_files(
555
- mermaid_data,
556
- botrun_flow_lang_url,
557
- user_id,
558
- title,
559
- )
560
-
561
- logger.info(f"create_mermaid_diagram generated============> {html_url}")
562
- return html_url
563
-
564
- except Exception as e:
565
- logger.error(f"create_mermaid_diagram error: {e}", error=str(e), exc_info=True)
566
- return f"Error creating diagram URL: {str(e)}"
567
-
568
-
569
- @mcp.tool()
570
- async def current_date_time() -> str:
571
- """
572
- Get the current date and time in local timezone (Asia/Taipei).
573
-
574
- Important: You MUST call this current_date_time function when:
575
- 1. User's query contains time-related words such as:
576
- - today, now, current
577
- - this week, next week
578
- - this month, last month
579
- - this year, last year, next year
580
- - recent, lately
581
- - future, upcoming
582
- - past, previous
583
- 2. User asks about current events or latest information
584
- 3. User wants to know time-sensitive information
585
- 4. Queries involving relative time expressions
586
-
587
- Examples of when to use current_date_time:
588
- - "What's the weather today?"
589
- - "This month's stock market performance"
590
- - "Any recent news?"
591
- - "Economic growth from last year until now"
592
- - "Upcoming events for next week"
593
- - "This month's sales data"
594
-
595
- Args:
596
- botrun_flow_lang_url: Optional URL for the botrun flow lang API (not used for this tool)
597
- user_id: Optional user ID (not used for this tool)
598
-
599
- Returns:
600
- str: Current date and time in format "YYYY-MM-DD HH:MM Asia/Taipei"
601
- """
602
- try:
603
- logger.info("current_date_time called")
604
-
605
- local_tz = pytz.timezone("Asia/Taipei")
606
- local_time = datetime.now(local_tz)
607
- result = local_time.strftime("%Y-%m-%d %H:%M %Z")
608
-
609
- logger.info(f"current_date_time============> {result}")
610
- return result
611
-
612
- except Exception as e:
613
- logger.error(f"current_date_time error: {e}", error=str(e), exc_info=True)
614
- return f"Error: {e}"
615
-
616
-
617
- def format_dates(dt):
618
- """
619
- Format datetime for both Western and Taiwan formats
620
- Western format: yyyy-mm-dd hh:mm:ss
621
- Taiwan format: (yyyy-1911)-mm-dd hh:mm:ss
622
- """
623
- western_date = dt.strftime("%Y-%m-%d %H:%M:%S")
624
- taiwan_year = dt.year - 1911
625
- taiwan_date = f"{taiwan_year}-{dt.strftime('%m-%d %H:%M:%S')}"
626
-
627
- return {"western_date": western_date, "taiwan_date": taiwan_date}
628
-
629
-
630
- @mcp.tool()
631
- async def web_search(
632
- user_input: str,
633
- return_images: bool = False,
634
- ) -> dict:
635
- """
636
- Search the web for up-to-date information using Perplexity.
637
- This tool provides detailed answers with citations.
638
-
639
- Unless the user insists on multiple-round searches, this tool can search for multiple conditions at once, for example:
640
- - Good: web_search("Search for today's sports, financial, and political news")
641
- - Unnecessary: Making separate searches for sports, financial, and political news
642
-
643
- Time/Date Information Requirements:
644
- 1. MUST preserve any specific dates or time periods mentioned in the user's query
645
- 2. Include both the current time and any specific time references from the query
646
-
647
- Examples:
648
- - Basic query:
649
- User asks: "Population of Japan"
650
- web_search("Population of Japan")
651
- Returns: {
652
- "content": "According to the latest statistics, Japan's population is about 125 million...",
653
- "citations": [
654
- {"title": "Statistics Bureau of Japan", "url": "https://www.stat.go.jp/..."},
655
- {"title": "World Bank Data", "url": "https://data.worldbank.org/..."}
656
- ]
657
- }
658
-
659
- - Query with specific date:
660
- User asks: "Look up news from January 15, 2023"
661
- web_search("Look up news from January 15, 2023")
662
- Returns: {
663
- "content": "News from January 15, 2023 includes...",
664
- "citations": [
665
- {"title": "BBC News", "url": "https://www.bbc.com/..."},
666
- {"title": "Reuters", "url": "https://www.reuters.com/..."}
667
- ]
668
- }
669
-
670
- - Location-specific query:
671
- User asks: "Weather in Paris today"
672
- web_search("Weather in Paris today")
673
- Returns: {
674
- "content": "Today's weather in Paris shows...",
675
- "citations": [
676
- {"title": "Weather Service", "url": "https://www.weather.com/..."},
677
- {"title": "Meteorological Office", "url": "https://www.metoffice.gov.uk/..."}
678
- ]
679
- }
680
-
681
- Args:
682
- user_input: The search query or question you want to find information about.
683
- MUST include any specific time periods or dates from the original query.
684
- Examples of time formats to preserve:
685
- - Specific dates: "2025/1/1", "2023-12-31", "January 15, 2023"
686
- - Years: "2023"
687
- - Quarters/Months: "Q1", "January", "First quarter"
688
- - Time periods: "past three years", "next five years"
689
- - Relative time: "yesterday", "next week", "last month"
690
- return_images: Whether to include images in search results. Set to True when you need to search for and return images along with text content.
691
- botrun_flow_lang_url: Optional URL for the botrun flow lang API (not used for this tool)
692
- user_id: Optional user ID (not used for this tool)
693
-
694
- Returns:
695
- dict: A dictionary containing:
696
- - content (str): The detailed answer based on web search results
697
- - citations (list): A list of URLs, citations are important to provide to the user
698
- - images (list): A list of image URLs (only when return_images is True)
699
- """
700
- try:
701
- logger.info(f"web_search user_input: {user_input}")
702
-
703
- now = datetime.now()
704
- dates = format_dates(now)
705
- western_date = dates["western_date"]
706
- taiwan_date = dates["taiwan_date"]
707
-
708
- logger.info(f"western_date: {western_date} taiwan_date: {taiwan_date}")
709
-
710
- # Format input with current time context (English only)
711
- final_input = (
712
- f"The current date: {western_date}\nThe user's question is: {user_input}"
713
- )
714
-
715
- # Process search using async generator
716
- search_result = {
717
- "content": "",
718
- "citations": [],
719
- }
720
-
721
- async for event in respond_with_perplexity_search(
722
- final_input,
723
- user_prompt_prefix="",
724
- messages_for_llm=[],
725
- domain_filter=[],
726
- stream=False,
727
- structured_output=True,
728
- return_images=return_images,
729
- ):
730
- if event and isinstance(event.chunk, str):
731
- search_result = json.loads(event.chunk)
732
-
733
- logger.info(
734
- f"web_search completed============> {len(search_result.get('content', ''))}"
735
- )
736
- return (
737
- search_result
738
- if search_result
739
- else {"content": "No results found.", "citations": []}
740
- )
741
-
742
- except Exception as e:
743
- logger.error(f"web_search error: {e}", error=str(e), exc_info=True)
744
- return {"content": f"Error during web search: {str(e)}", "citations": []}
1
+ import os
2
+ import asyncio
3
+ import json
4
+ import pytz
5
+ from datetime import datetime
6
+ from typing import Dict, Any, Optional
7
+
8
+ # Import for web_search
9
+ from botrun_flow_lang.langgraph_agents.agents.util.perplexity_search import (
10
+ respond_with_perplexity_search,
11
+ )
12
+
13
+ from mcp.server.fastmcp import FastMCP
14
+ from langchain_core.runnables import RunnableConfig
15
+
16
+ # Import necessary dependencies
17
+ from botrun_flow_lang.models.nodes.utils import scrape_single_url
18
+ from botrun_flow_lang.langgraph_agents.agents.util.pdf_analyzer import (
19
+ analyze_pdf_async,
20
+ )
21
+ from botrun_flow_lang.langgraph_agents.agents.util.img_util import analyze_imgs
22
+ from botrun_flow_lang.langgraph_agents.agents.util.local_files import (
23
+ upload_and_get_tmp_public_url,
24
+ )
25
+ from botrun_flow_lang.langgraph_agents.agents.util.html_util import generate_html_file
26
+ from botrun_flow_lang.langgraph_agents.agents.util.plotly_util import (
27
+ generate_plotly_files,
28
+ )
29
+ from botrun_flow_lang.langgraph_agents.agents.util.mermaid_util import (
30
+ generate_mermaid_files,
31
+ )
32
+ from botrun_flow_lang.utils.clients.rate_limit_client import RateLimitClient
33
+ from botrun_flow_lang.utils.botrun_logger import get_default_botrun_logger
34
+
35
+ # Import for generate_image
36
+ from langchain_community.utilities.dalle_image_generator import DallEAPIWrapper
37
+ from langchain_community.callbacks import get_openai_callback
38
+
39
+ # Initialize MCP server
40
+ mcp = FastMCP(name="BotrunFlowLangDefaultMCP", stateless_http=True)
41
+
42
+ # Initialize logger - reuse the default instance
43
+ logger = get_default_botrun_logger()
44
+
45
+
46
+ # Exception class for rate limit errors
47
+ class BotrunRateLimitException(Exception):
48
+ """Exception that should be displayed directly to the user."""
49
+
50
+ def __init__(self, message):
51
+ self.message = f"[Please tell user error] {message}"
52
+ super().__init__(self.message)
53
+
54
+
55
+ @mcp.tool()
56
+ async def scrape(url: str) -> dict:
57
+ """
58
+ Scrape a web page to extract its content.
59
+
60
+ Args:
61
+ url: The URL to scrape
62
+
63
+ Returns:
64
+ dict: A dictionary containing the scraped content or error message
65
+ """
66
+ try:
67
+ logger.info(f"scrape {url}")
68
+ return await scrape_single_url(url)
69
+ except Exception as e:
70
+ logger.error(f"scrape {url} error: {e}", error=str(e), exc_info=True)
71
+ return {"url": url, "status": "error", "error": str(e)}
72
+
73
+
74
+ @mcp.tool()
75
+ async def chat_with_pdf(
76
+ pdf_url: str, user_input: str, botrun_flow_lang_url: str, user_id: str
77
+ ) -> str:
78
+ """
79
+ Analyze a PDF file and answer questions about its content.
80
+
81
+ Supports intelligent processing based on file size:
82
+ - Small files (< 5MB): Direct multimodal analysis
83
+ - Large files (>= 5MB): Compress -> Split -> Parallel multimodal Q&A -> Merge results
84
+
85
+ Args:
86
+ pdf_url: The URL to the PDF file (can be generated using generate_tmp_public_url for local files)
87
+ user_input: The user's question or instruction about the PDF content
88
+ botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
89
+ user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
90
+
91
+ Returns:
92
+ str: Analysis result or Plotly-compatible data structure if visualization is needed
93
+ """
94
+ logger.info(f"chat_with_pdf pdf_url: {pdf_url} user_input: {user_input}")
95
+
96
+ # Convert local file path to public URL if needed
97
+ if not pdf_url.startswith("http"):
98
+ pdf_url = upload_and_get_tmp_public_url(pdf_url, botrun_flow_lang_url, user_id)
99
+
100
+ return await analyze_pdf_async(pdf_url, user_input)
101
+
102
+
103
+ @mcp.tool()
104
+ async def chat_with_imgs(
105
+ img_urls: list[str],
106
+ user_input: str,
107
+ botrun_flow_lang_url: str,
108
+ user_id: str,
109
+ ) -> str:
110
+ """
111
+ Analyze multiple images and answer questions about their content.
112
+
113
+ Args:
114
+ img_urls: List of URLs to the image files (can be generated using generate_tmp_public_url for local files)
115
+ user_input: Question or instruction about the image content(s)
116
+ botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
117
+ user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
118
+
119
+ Returns:
120
+ str: Analysis result or Plotly-compatible data structure if visualization is needed
121
+ """
122
+ logger.info(f"chat_with_imgs img_urls: {img_urls} user_input: {user_input}")
123
+
124
+ # Convert local file paths to public URLs if needed
125
+ new_img_urls = []
126
+ for img_url in img_urls:
127
+ if not img_url.startswith("http"):
128
+ img_url = upload_and_get_tmp_public_url(
129
+ img_url, botrun_flow_lang_url, user_id
130
+ )
131
+ new_img_urls.append(img_url)
132
+
133
+ return analyze_imgs(new_img_urls, user_input)
134
+
135
+
136
+ @mcp.tool()
137
+ async def generate_image(
138
+ user_input: str, user_id: str = "", botrun_flow_lang_url: str = ""
139
+ ) -> str:
140
+ """
141
+ Generate high-quality images using DALL-E 3 and store permanently in GCS.
142
+
143
+ When using generate_image tool, you must include the image URL in your response.
144
+ You MUST respond using this format (from @begin img to @end, including the image URL):
145
+ @begin img("{image_url}") @end
146
+
147
+ Capabilities:
148
+ - Creates photorealistic images and art
149
+ - Handles complex scenes and compositions
150
+ - Maintains consistent styles
151
+ - Follows detailed prompts with high accuracy
152
+ - Supports various artistic styles and mediums
153
+
154
+ Best practices for prompts:
155
+ - Be specific about style, mood, lighting, and composition
156
+ - Include details about perspective and setting
157
+ - Specify artistic medium if desired (e.g., "oil painting", "digital art")
158
+ - Mention color schemes or specific colors
159
+ - Describe the atmosphere or emotion you want to convey
160
+
161
+ Limitations:
162
+ - Cannot generate images of public figures or celebrities
163
+ - Avoids harmful, violent, or adult content
164
+ - May have inconsistencies with hands, faces, or text
165
+ - Cannot generate exact copies of existing artworks or brands
166
+ - Limited to single image generation per request
167
+ - Subject to daily usage limits
168
+
169
+ Args:
170
+ user_input: Detailed description of the image you want to generate.
171
+ Be specific about style, content, and composition.
172
+ user_id: REQUIRED - User ID for rate limit and file storage (LLM can get this from system prompt)
173
+ botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
174
+
175
+ Returns:
176
+ str: Permanent URL to the generated image stored in GCS, or error message if generation fails
177
+ """
178
+ try:
179
+ logger.info(f"generate_image user_input: {user_input}")
180
+
181
+ # 驗證必要參數
182
+ if not user_id:
183
+ logger.error("User ID not available")
184
+ return "User ID not available"
185
+ if not botrun_flow_lang_url:
186
+ logger.error("botrun_flow_lang_url not available")
187
+ return "botrun_flow_lang_url not available"
188
+
189
+ # Check rate limit before generating image
190
+ rate_limit_client = RateLimitClient()
191
+ rate_limit_info = await rate_limit_client.get_rate_limit(user_id)
192
+
193
+ # Check if user can generate an image
194
+ drawing_info = rate_limit_info.get("drawing", {})
195
+ can_use = drawing_info.get("can_use", False)
196
+
197
+ if not can_use:
198
+ daily_limit = drawing_info.get("daily_limit", 0)
199
+ current_usage = drawing_info.get("current_usage", 0)
200
+ logger.error(
201
+ f"User {user_id} has reached daily limit of {daily_limit} image generations. "
202
+ f"Current usage: {current_usage}. Please try again tomorrow."
203
+ )
204
+ return f"[Please tell user error] You have reached your daily limit of {daily_limit} image generations. " \
205
+ f"Current usage: {current_usage}. Please try again tomorrow."
206
+ # raise BotrunRateLimitException(
207
+ # f"You have reached your daily limit of {daily_limit} image generations. "
208
+ # f"Current usage: {current_usage}. Please try again tomorrow."
209
+ # )
210
+
211
+ # 2. 使用 DALL-E 生成圖片
212
+ dalle_wrapper = DallEAPIWrapper(
213
+ api_key=os.getenv("OPENAI_API_KEY"), model="dall-e-3"
214
+ )
215
+
216
+ # Generate image with token usage tracking
217
+ with get_openai_callback() as cb:
218
+ temp_image_url = dalle_wrapper.run(user_input)
219
+ logger.info(
220
+ f"DALL-E generated temporary URL: {temp_image_url}, "
221
+ f"prompt tokens: {cb.prompt_tokens}, "
222
+ f"completion tokens: {cb.completion_tokens}"
223
+ )
224
+
225
+ # 3. 下載並上傳到 GCS,取得永久 URL
226
+ from botrun_flow_lang.langgraph_agents.agents.util.local_files import (
227
+ upload_image_and_get_public_url,
228
+ )
229
+
230
+ try:
231
+ permanent_url = await upload_image_and_get_public_url(
232
+ temp_image_url, botrun_flow_lang_url, user_id
233
+ )
234
+ logger.info(f"Image uploaded to GCS: {permanent_url}")
235
+
236
+ # 4. 更新使用計數
237
+ await rate_limit_client.update_drawing_usage(user_id)
238
+
239
+ return permanent_url
240
+ except Exception as upload_error:
241
+ logger.error(
242
+ f"Failed to upload to GCS, returning temporary URL: {upload_error}"
243
+ )
244
+ # Fallback: 回傳臨時 URL
245
+ await rate_limit_client.update_drawing_usage(user_id)
246
+ return temp_image_url
247
+
248
+ except Exception as e:
249
+ logger.error(f"generate_image error: {e}", error=str(e), exc_info=True)
250
+
251
+ # Check if this is a user-visible exception
252
+ if str(e).startswith("[Please tell user error]"):
253
+ return str(e) # Return the error message as is
254
+ return f"Error: {e}"
255
+
256
+
257
+ @mcp.tool()
258
+ async def generate_tmp_public_url(
259
+ file_path: str, botrun_flow_lang_url: str, user_id: str
260
+ ) -> str:
261
+ """
262
+ Create a temporary public URL for a local file.
263
+
264
+ Args:
265
+ file_path: The path to the local file you want to make publicly accessible
266
+ botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
267
+ user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
268
+
269
+ Returns:
270
+ str: A public URL that can be used to access the file for 7 days
271
+
272
+ Raises:
273
+ FileNotFoundError: If the specified file does not exist
274
+ """
275
+ logger.info(f"generate_tmp_public_url file_path: {file_path}")
276
+
277
+ if not os.path.exists(file_path):
278
+ return f"File not found: {file_path}"
279
+ # raise FileNotFoundError(f"File not found: {file_path}")
280
+
281
+ return upload_and_get_tmp_public_url(file_path, botrun_flow_lang_url, user_id)
282
+
283
+
284
+ @mcp.tool()
285
+ async def create_html_page(
286
+ html_content: str,
287
+ title: str,
288
+ botrun_flow_lang_url: str,
289
+ user_id: str,
290
+ ) -> str:
291
+ """
292
+ Create a custom HTML page and return its URL.
293
+
294
+ This tool supports complete HTML documents, including JavaScript and CSS, which can be used to create
295
+ complex interactive pages.
296
+
297
+ Prioritize using the following frameworks for writing HTML:
298
+ - DataTables for tables (include in header: <link rel="stylesheet" href="https://cdn.datatables.net/2.3.3/css/dataTables.dataTables.css" /> and <script src="https://cdn.datatables.net/2.3.3/js/dataTables.js"></script>)
299
+ - Alpine.js for interactivity (include in header: <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>)
300
+ - Tailwind CSS for styling (include in header: <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>)
301
+ - daisyUI for UI components (include in header: <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />)
302
+ - Chart.js for charts (include in header: <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>)
303
+ - Animate.css for animations (include in header: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />)
304
+
305
+ Input Options:
306
+ You can pass either:
307
+ 1. A complete HTML document with doctype, html, head, and body tags
308
+ 2. An HTML fragment that will be automatically wrapped in a basic HTML structure
309
+
310
+ Args:
311
+ html_content: Complete HTML document or fragment. Can include JavaScript, CSS, and other elements.
312
+ title: Optional title for the HTML page
313
+ botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
314
+ user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
315
+
316
+ Returns:
317
+ str: URL for the HTML page. This URL should be provided to the user,
318
+ as they will need to access it to view the content in their web browser.
319
+ """
320
+ try:
321
+ logger.info(f"create_html_page html_content: {html_content} title: {title}")
322
+
323
+ html_url = await generate_html_file(
324
+ html_content, botrun_flow_lang_url, user_id, title
325
+ )
326
+
327
+ logger.info(f"create_html_page generated============> {html_url}")
328
+ return html_url
329
+
330
+ except Exception as e:
331
+ logger.error(f"create_html_page error: {e}", error=str(e), exc_info=True)
332
+ return f"Error creating HTML page URL: {str(e)}"
333
+
334
+
335
+ @mcp.tool()
336
+ async def create_plotly_chart(
337
+ figure_data: str | dict,
338
+ title: str,
339
+ botrun_flow_lang_url: str,
340
+ user_id: str,
341
+ ) -> str:
342
+ """
343
+ Create an interactive Plotly visualization and return its URL.
344
+ This URL should be provided to the user,
345
+ as they will need to access it to view the interactive chart in their web browser.
346
+
347
+ Scenarios for using create_plotly_chart:
348
+ - Need to create data visualizations and charts
349
+ - Need to show data trends (line charts)
350
+ - Need to compare values (bar charts, pie charts)
351
+ - Need to show distributions (scatter plots, heat maps)
352
+ - Need to display time series data (timeline charts)
353
+ - Need to show geographic information (maps)
354
+ - Need to perform multidimensional data analysis (3D charts, bubble charts)
355
+ - Need to show statistical distributions (box plots)
356
+ - Need to show cumulative trends (area charts)
357
+ - Need interactive data exploration
358
+
359
+ Integration with Other Tools:
360
+ This function can be used in conjunction with chat_with_imgs and chat_with_pdf when they return data
361
+ suitable for visualization. When those tools detect a need for visualization, they will return a JSON string
362
+ with a "__plotly_data__" key, which can be directly passed to this function.
363
+
364
+ Example workflow:
365
+ 1. User asks to analyze and visualize data from images/PDFs
366
+ 2. chat_with_imgs or chat_with_pdf returns JSON string with "__plotly_data__" key
367
+ 3. Pass that string to this function to get an interactive visualization URL
368
+
369
+ Supported Chart Types:
370
+ - Line charts: For showing trends and time series data
371
+ - Bar charts: For comparing values across categories
372
+ - Pie charts: For showing proportions of a whole
373
+ - Scatter plots: For showing relationships between variables
374
+ - Heat maps: For showing patterns in matrix data
375
+ - Box plots: For showing statistical distributions
376
+ - Geographic maps: For showing spatial data
377
+ - 3D plots: For showing three-dimensional data
378
+ - Bubble charts: For showing three variables in 2D
379
+ - Area charts: For showing cumulative totals over time
380
+
381
+ The figure_data can be either:
382
+ 1. A JSON string containing plotly figure specifications with 'data' and 'layout'
383
+ 2. A dictionary object with plotly figure specifications
384
+
385
+ Example JSON string:
386
+ '{"data": [{"type": "scatter", "x": [1, 2, 3, 4], "y": [10, 15, 13, 17]}], "layout": {"title": "My Plot"}}'
387
+
388
+ Example dictionary:
389
+ {
390
+ 'data': [{
391
+ 'type': 'scatter',
392
+ 'x': [1, 2, 3, 4],
393
+ 'y': [10, 15, 13, 17]
394
+ }],
395
+ 'layout': {
396
+ 'title': 'My Plot'
397
+ }
398
+ }
399
+
400
+ Args:
401
+ figure_data: JSON string OR dictionary containing plotly figure specifications or output from chat_with_imgs/chat_with_pdf.
402
+ String inputs will be parsed using json.loads(), dictionary inputs will be used directly.
403
+ title: Optional title for the plot.
404
+ botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
405
+ user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
406
+
407
+ Returns:
408
+ str: URL for the interactive HTML visualization. This URL should be provided to the user,
409
+ as they will need to access it to view the interactive chart in their web browser.
410
+ """
411
+ try:
412
+ logger.info(f"create_plotly_chart figure_data: {figure_data} title: {title}")
413
+
414
+ # Handle both string and dict inputs
415
+ if isinstance(figure_data, str):
416
+ figure_dict = json.loads(figure_data)
417
+ else:
418
+ figure_dict = figure_data
419
+
420
+ # If the input is from chat_with_imgs or chat_with_pdf, extract the plotly data
421
+ if "__plotly_data__" in figure_dict:
422
+ figure_dict = figure_dict["__plotly_data__"]
423
+
424
+ html_url = await generate_plotly_files(
425
+ figure_dict,
426
+ botrun_flow_lang_url,
427
+ user_id,
428
+ title,
429
+ )
430
+
431
+ logger.info(f"create_plotly_chart generated============> {html_url}")
432
+ return html_url
433
+
434
+ except json.JSONDecodeError as e:
435
+ logger.error(
436
+ f"create_plotly_chart JSON parsing error: {e}", error=str(e), exc_info=True
437
+ )
438
+ return f"Error parsing JSON figure_data: {str(e)}. Please ensure figure_data is a valid JSON string or dictionary."
439
+ except KeyError as e:
440
+ logger.error(
441
+ f"create_plotly_chart missing key error: {e}", error=str(e), exc_info=True
442
+ )
443
+ return f"Error: Missing required key in figure data: {str(e)}. Please ensure figure_data contains 'data' and 'layout' keys."
444
+ except Exception as e:
445
+ logger.error(f"create_plotly_chart error: {e}", error=str(e), exc_info=True)
446
+ return f"Error creating visualization URL: {str(e)}"
447
+
448
+
449
+ @mcp.tool()
450
+ async def create_mermaid_diagram(
451
+ mermaid_data: str,
452
+ title: str,
453
+ botrun_flow_lang_url: str,
454
+ user_id: str,
455
+ ) -> str:
456
+ """
457
+ Create an interactive Mermaid diagram visualization and return its URL.
458
+ This URL should be provided to the user,
459
+ as they will need to access it to view the interactive diagram in their web browser.
460
+
461
+ Scenarios for using create_mermaid_diagram:
462
+ - Need to visualize flowcharts, architecture diagrams, or relationship diagrams
463
+ - Need to show system architecture (flowchart)
464
+ - Need to explain operational processes (flowchart)
465
+ - Need to show sequence interactions (sequence diagram)
466
+ - Need to show state transitions (state diagram)
467
+ - Need to show class relationships (class diagram)
468
+ - Need to show entity relationships (ER diagram)
469
+ - Need to show project timelines (gantt chart)
470
+ - Need to show user journeys (journey)
471
+ - Need to show requirement relationships (requirement diagram)
472
+ - Need to show resource allocation (pie chart)
473
+
474
+ Supported Diagram Types:
475
+ 1. Flowcharts (graph TD/LR):
476
+ - System architectures
477
+ - Process flows
478
+ - Decision trees
479
+ - Data flows
480
+
481
+ 2. Sequence Diagrams (sequenceDiagram):
482
+ - API interactions
483
+ - System communications
484
+ - User interactions
485
+ - Message flows
486
+
487
+ 3. Class Diagrams (classDiagram):
488
+ - Software architecture
489
+ - Object relationships
490
+ - System components
491
+ - Code structure
492
+
493
+ 4. State Diagrams (stateDiagram-v2):
494
+ - System states
495
+ - Workflow states
496
+ - Process states
497
+ - State transitions
498
+
499
+ 5. Entity Relationship Diagrams (erDiagram):
500
+ - Database schemas
501
+ - Data relationships
502
+ - System entities
503
+ - Data models
504
+
505
+ 6. User Journey Diagrams (journey):
506
+ - User experiences
507
+ - Customer flows
508
+ - Process steps
509
+ - Task sequences
510
+
511
+ 7. Gantt Charts (gantt):
512
+ - Project timelines
513
+ - Task schedules
514
+ - Resource allocation
515
+ - Milestone tracking
516
+
517
+ 8. Pie Charts (pie):
518
+ - Data distribution
519
+ - Resource allocation
520
+ - Market share
521
+ - Component breakdown
522
+
523
+ 9. Requirement Diagrams (requirementDiagram):
524
+ - System requirements
525
+ - Dependencies
526
+ - Specifications
527
+ - Constraints
528
+
529
+ Example Mermaid syntax for a simple flowchart:
530
+ ```
531
+ graph TD
532
+ A[Start] --> B{Data Available?}
533
+ B -->|Yes| C[Process Data]
534
+ B -->|No| D[Get Data]
535
+ C --> E[End]
536
+ D --> B
537
+ ```
538
+
539
+ Args:
540
+ mermaid_data: String containing the Mermaid diagram definition
541
+ title: Optional title for the diagram
542
+ botrun_flow_lang_url: REQUIRED - URL for the botrun flow lang API (LLM can get this from system prompt)
543
+ user_id: REQUIRED - User ID for file upload (LLM can get this from system prompt)
544
+
545
+ Returns:
546
+ str: URL for the interactive HTML visualization. This URL should be provided to the user,
547
+ as they will need to access it to view the interactive diagram in their web browser.
548
+ """
549
+ try:
550
+ logger.info(
551
+ f"create_mermaid_diagram mermaid_data: {mermaid_data} title: {title}"
552
+ )
553
+
554
+ html_url = await generate_mermaid_files(
555
+ mermaid_data,
556
+ botrun_flow_lang_url,
557
+ user_id,
558
+ title,
559
+ )
560
+
561
+ logger.info(f"create_mermaid_diagram generated============> {html_url}")
562
+ return html_url
563
+
564
+ except Exception as e:
565
+ logger.error(f"create_mermaid_diagram error: {e}", error=str(e), exc_info=True)
566
+ return f"Error creating diagram URL: {str(e)}"
567
+
568
+
569
+ @mcp.tool()
570
+ async def current_date_time() -> str:
571
+ """
572
+ Get the current date and time in local timezone (Asia/Taipei).
573
+
574
+ Important: You MUST call this current_date_time function when:
575
+ 1. User's query contains time-related words such as:
576
+ - today, now, current
577
+ - this week, next week
578
+ - this month, last month
579
+ - this year, last year, next year
580
+ - recent, lately
581
+ - future, upcoming
582
+ - past, previous
583
+ 2. User asks about current events or latest information
584
+ 3. User wants to know time-sensitive information
585
+ 4. Queries involving relative time expressions
586
+
587
+ Examples of when to use current_date_time:
588
+ - "What's the weather today?"
589
+ - "This month's stock market performance"
590
+ - "Any recent news?"
591
+ - "Economic growth from last year until now"
592
+ - "Upcoming events for next week"
593
+ - "This month's sales data"
594
+
595
+ Args:
596
+ botrun_flow_lang_url: Optional URL for the botrun flow lang API (not used for this tool)
597
+ user_id: Optional user ID (not used for this tool)
598
+
599
+ Returns:
600
+ str: Current date and time in format "YYYY-MM-DD HH:MM Asia/Taipei"
601
+ """
602
+ try:
603
+ logger.info("current_date_time called")
604
+
605
+ local_tz = pytz.timezone("Asia/Taipei")
606
+ local_time = datetime.now(local_tz)
607
+ result = local_time.strftime("%Y-%m-%d %H:%M %Z")
608
+
609
+ logger.info(f"current_date_time============> {result}")
610
+ return result
611
+
612
+ except Exception as e:
613
+ logger.error(f"current_date_time error: {e}", error=str(e), exc_info=True)
614
+ return f"Error: {e}"
615
+
616
+
617
+ def format_dates(dt):
618
+ """
619
+ Format datetime for both Western and Taiwan formats
620
+ Western format: yyyy-mm-dd hh:mm:ss
621
+ Taiwan format: (yyyy-1911)-mm-dd hh:mm:ss
622
+ """
623
+ western_date = dt.strftime("%Y-%m-%d %H:%M:%S")
624
+ taiwan_year = dt.year - 1911
625
+ taiwan_date = f"{taiwan_year}-{dt.strftime('%m-%d %H:%M:%S')}"
626
+
627
+ return {"western_date": western_date, "taiwan_date": taiwan_date}
628
+
629
+
630
+ @mcp.tool()
631
+ async def web_search(
632
+ user_input: str,
633
+ return_images: bool = False,
634
+ ) -> dict:
635
+ """
636
+ Search the web for up-to-date information using Perplexity.
637
+ This tool provides detailed answers with citations.
638
+
639
+ Unless the user insists on multiple-round searches, this tool can search for multiple conditions at once, for example:
640
+ - Good: web_search("Search for today's sports, financial, and political news")
641
+ - Unnecessary: Making separate searches for sports, financial, and political news
642
+
643
+ Time/Date Information Requirements:
644
+ 1. MUST preserve any specific dates or time periods mentioned in the user's query
645
+ 2. Include both the current time and any specific time references from the query
646
+
647
+ Examples:
648
+ - Basic query:
649
+ User asks: "Population of Japan"
650
+ web_search("Population of Japan")
651
+ Returns: {
652
+ "content": "According to the latest statistics, Japan's population is about 125 million...",
653
+ "citations": [
654
+ {"title": "Statistics Bureau of Japan", "url": "https://www.stat.go.jp/..."},
655
+ {"title": "World Bank Data", "url": "https://data.worldbank.org/..."}
656
+ ]
657
+ }
658
+
659
+ - Query with specific date:
660
+ User asks: "Look up news from January 15, 2023"
661
+ web_search("Look up news from January 15, 2023")
662
+ Returns: {
663
+ "content": "News from January 15, 2023 includes...",
664
+ "citations": [
665
+ {"title": "BBC News", "url": "https://www.bbc.com/..."},
666
+ {"title": "Reuters", "url": "https://www.reuters.com/..."}
667
+ ]
668
+ }
669
+
670
+ - Location-specific query:
671
+ User asks: "Weather in Paris today"
672
+ web_search("Weather in Paris today")
673
+ Returns: {
674
+ "content": "Today's weather in Paris shows...",
675
+ "citations": [
676
+ {"title": "Weather Service", "url": "https://www.weather.com/..."},
677
+ {"title": "Meteorological Office", "url": "https://www.metoffice.gov.uk/..."}
678
+ ]
679
+ }
680
+
681
+ Args:
682
+ user_input: The search query or question you want to find information about.
683
+ MUST include any specific time periods or dates from the original query.
684
+ Examples of time formats to preserve:
685
+ - Specific dates: "2025/1/1", "2023-12-31", "January 15, 2023"
686
+ - Years: "2023"
687
+ - Quarters/Months: "Q1", "January", "First quarter"
688
+ - Time periods: "past three years", "next five years"
689
+ - Relative time: "yesterday", "next week", "last month"
690
+ return_images: Whether to include images in search results. Set to True when you need to search for and return images along with text content.
691
+ botrun_flow_lang_url: Optional URL for the botrun flow lang API (not used for this tool)
692
+ user_id: Optional user ID (not used for this tool)
693
+
694
+ Returns:
695
+ dict: A dictionary containing:
696
+ - content (str): The detailed answer based on web search results
697
+ - citations (list): A list of URLs, citations are important to provide to the user
698
+ - images (list): A list of image URLs (only when return_images is True)
699
+ """
700
+ try:
701
+ logger.info(f"web_search user_input: {user_input}")
702
+
703
+ now = datetime.now()
704
+ dates = format_dates(now)
705
+ western_date = dates["western_date"]
706
+ taiwan_date = dates["taiwan_date"]
707
+
708
+ logger.info(f"western_date: {western_date} taiwan_date: {taiwan_date}")
709
+
710
+ # Format input with current time context (English only)
711
+ final_input = (
712
+ f"The current date: {western_date}\nThe user's question is: {user_input}"
713
+ )
714
+
715
+ # Process search using async generator
716
+ search_result = {
717
+ "content": "",
718
+ "citations": [],
719
+ }
720
+
721
+ async for event in respond_with_perplexity_search(
722
+ final_input,
723
+ user_prompt_prefix="",
724
+ messages_for_llm=[],
725
+ domain_filter=[],
726
+ stream=False,
727
+ structured_output=True,
728
+ return_images=return_images,
729
+ ):
730
+ if event and isinstance(event.chunk, str):
731
+ search_result = json.loads(event.chunk)
732
+
733
+ logger.info(
734
+ f"web_search completed============> {len(search_result.get('content', ''))}"
735
+ )
736
+ return (
737
+ search_result
738
+ if search_result
739
+ else {"content": "No results found.", "citations": []}
740
+ )
741
+
742
+ except Exception as e:
743
+ logger.error(f"web_search error: {e}", error=str(e), exc_info=True)
744
+ return {"content": f"Error during web search: {str(e)}", "citations": []}