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.
- botrun_flow_lang/api/auth_api.py +39 -39
- botrun_flow_lang/api/auth_utils.py +183 -183
- botrun_flow_lang/api/botrun_back_api.py +65 -65
- botrun_flow_lang/api/flow_api.py +3 -3
- botrun_flow_lang/api/hatch_api.py +508 -508
- botrun_flow_lang/api/langgraph_api.py +811 -811
- botrun_flow_lang/api/line_bot_api.py +1484 -1484
- botrun_flow_lang/api/model_api.py +300 -300
- botrun_flow_lang/api/rate_limit_api.py +32 -32
- botrun_flow_lang/api/routes.py +79 -79
- botrun_flow_lang/api/search_api.py +53 -53
- botrun_flow_lang/api/storage_api.py +395 -395
- botrun_flow_lang/api/subsidy_api.py +290 -290
- botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
- botrun_flow_lang/api/user_setting_api.py +70 -70
- botrun_flow_lang/api/version_api.py +31 -31
- botrun_flow_lang/api/youtube_api.py +26 -26
- botrun_flow_lang/constants.py +13 -13
- botrun_flow_lang/langgraph_agents/agents/agent_runner.py +178 -178
- botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
- botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gemini_subsidy_graph.py +460 -460
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
- botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +723 -723
- botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
- botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
- botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
- botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
- botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
- botrun_flow_lang/langgraph_agents/agents/util/img_util.py +294 -294
- botrun_flow_lang/langgraph_agents/agents/util/local_files.py +419 -419
- botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
- botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
- botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +486 -486
- botrun_flow_lang/langgraph_agents/agents/util/pdf_cache.py +250 -250
- botrun_flow_lang/langgraph_agents/agents/util/pdf_processor.py +204 -204
- botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
- botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
- botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
- botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
- botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
- botrun_flow_lang/llm_agent/llm_agent.py +19 -19
- botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
- botrun_flow_lang/log/.gitignore +2 -2
- botrun_flow_lang/main.py +61 -61
- botrun_flow_lang/main_fast.py +51 -51
- botrun_flow_lang/mcp_server/__init__.py +10 -10
- botrun_flow_lang/mcp_server/default_mcp.py +744 -744
- botrun_flow_lang/models/nodes/utils.py +205 -205
- botrun_flow_lang/models/token_usage.py +34 -34
- botrun_flow_lang/requirements.txt +21 -21
- botrun_flow_lang/services/base/firestore_base.py +30 -30
- botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
- botrun_flow_lang/services/hatch/hatch_fs_store.py +419 -387
- botrun_flow_lang/services/storage/storage_cs_store.py +206 -206
- botrun_flow_lang/services/storage/storage_factory.py +12 -12
- botrun_flow_lang/services/storage/storage_store.py +65 -65
- botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
- botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
- botrun_flow_lang/static/docs/tools/index.html +926 -926
- botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
- botrun_flow_lang/tests/api_stress_test.py +357 -357
- botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
- botrun_flow_lang/tests/test_botrun_app.py +46 -46
- botrun_flow_lang/tests/test_html_util.py +31 -31
- botrun_flow_lang/tests/test_img_analyzer.py +190 -190
- botrun_flow_lang/tests/test_img_util.py +39 -39
- botrun_flow_lang/tests/test_local_files.py +114 -114
- botrun_flow_lang/tests/test_mermaid_util.py +103 -103
- botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
- botrun_flow_lang/tests/test_plotly_util.py +151 -151
- botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
- botrun_flow_lang/tools/generate_docs.py +133 -133
- botrun_flow_lang/tools/templates/tools.html +153 -153
- botrun_flow_lang/utils/__init__.py +7 -7
- botrun_flow_lang/utils/botrun_logger.py +344 -344
- botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
- botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
- botrun_flow_lang/utils/google_drive_utils.py +654 -654
- botrun_flow_lang/utils/langchain_utils.py +324 -324
- botrun_flow_lang/utils/yaml_utils.py +9 -9
- {botrun_flow_lang-5.12.262.dist-info → botrun_flow_lang-5.12.264.dist-info}/METADATA +1 -1
- botrun_flow_lang-5.12.264.dist-info/RECORD +102 -0
- botrun_flow_lang-5.12.262.dist-info/RECORD +0 -102
- {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": []}
|