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