botrun-flow-lang 5.10.82__py3-none-any.whl → 5.10.83__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 +481 -481
- botrun_flow_lang/api/langgraph_api.py +796 -796
- botrun_flow_lang/api/line_bot_api.py +1357 -1357
- 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 +316 -316
- 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 +174 -174
- 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/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 +591 -548
- 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 +345 -345
- 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 +160 -160
- 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 +711 -711
- 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 +372 -372
- botrun_flow_lang/services/storage/storage_cs_store.py +202 -202
- 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.10.82.dist-info → botrun_flow_lang-5.10.83.dist-info}/METADATA +3 -2
- botrun_flow_lang-5.10.83.dist-info/RECORD +99 -0
- botrun_flow_lang-5.10.82.dist-info/RECORD +0 -99
- {botrun_flow_lang-5.10.82.dist-info → botrun_flow_lang-5.10.83.dist-info}/WHEEL +0 -0
|
@@ -1,300 +1,300 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import pandas as pd
|
|
3
|
-
from fastapi import APIRouter, HTTPException
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
from typing import List, Optional
|
|
6
|
-
from google.oauth2 import service_account
|
|
7
|
-
from googleapiclient.discovery import build
|
|
8
|
-
|
|
9
|
-
router = APIRouter()
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ModelInfo(BaseModel):
|
|
13
|
-
"""Model information structure"""
|
|
14
|
-
|
|
15
|
-
display_name: str
|
|
16
|
-
model_name: str
|
|
17
|
-
provider: str
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ModelsResponse(BaseModel):
|
|
21
|
-
"""Response model for listing supported models"""
|
|
22
|
-
|
|
23
|
-
models: List[ModelInfo]
|
|
24
|
-
total_count: int
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class AgentModelsResponse(BaseModel):
|
|
28
|
-
"""Response model for listing supported agent models"""
|
|
29
|
-
|
|
30
|
-
models: List[str]
|
|
31
|
-
total_count: int
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def read_google_sheet(
|
|
35
|
-
credentials_path: str, spreadsheet_id: str, sheet_name: str
|
|
36
|
-
) -> pd.DataFrame:
|
|
37
|
-
"""
|
|
38
|
-
讀取 Google Spreadsheet 指定 sheet,回傳 pandas DataFrame。
|
|
39
|
-
"""
|
|
40
|
-
try:
|
|
41
|
-
scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
|
|
42
|
-
credentials = service_account.Credentials.from_service_account_file(
|
|
43
|
-
credentials_path, scopes=scopes
|
|
44
|
-
)
|
|
45
|
-
service = build("sheets", "v4", credentials=credentials)
|
|
46
|
-
sheet = service.spreadsheets()
|
|
47
|
-
result = (
|
|
48
|
-
sheet.values().get(spreadsheetId=spreadsheet_id, range=sheet_name).execute()
|
|
49
|
-
)
|
|
50
|
-
values = result.get("values", [])
|
|
51
|
-
if not values:
|
|
52
|
-
return pd.DataFrame()
|
|
53
|
-
# 第一列為欄位名稱
|
|
54
|
-
df = pd.DataFrame(values[1:], columns=values[0])
|
|
55
|
-
return df
|
|
56
|
-
except Exception:
|
|
57
|
-
import traceback
|
|
58
|
-
|
|
59
|
-
traceback.print_exc()
|
|
60
|
-
return pd.DataFrame()
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def get_models_from_google_sheet() -> List[ModelInfo]:
|
|
64
|
-
"""
|
|
65
|
-
從 Google Sheets 讀取模型列表
|
|
66
|
-
優先順序:ENV_NAME sheet -> default sheet -> DEFAULT_SUPPORTED_MODELS
|
|
67
|
-
"""
|
|
68
|
-
credentials_path = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_FOR_MODELS_SHEET")
|
|
69
|
-
spreadsheet_id = os.getenv("MODELS_GSPREAD_ID")
|
|
70
|
-
env_name = os.getenv("ENV_NAME", "")
|
|
71
|
-
|
|
72
|
-
if not credentials_path or not spreadsheet_id:
|
|
73
|
-
return DEFAULT_SUPPORTED_MODELS
|
|
74
|
-
|
|
75
|
-
# 首先嘗試使用 ENV_NAME 作為 sheet_name
|
|
76
|
-
if env_name:
|
|
77
|
-
df = read_google_sheet(credentials_path, spreadsheet_id, env_name)
|
|
78
|
-
if not df.empty and all(
|
|
79
|
-
col in df.columns for col in ["display_name", "model_name", "provider"]
|
|
80
|
-
):
|
|
81
|
-
try:
|
|
82
|
-
return [
|
|
83
|
-
ModelInfo(
|
|
84
|
-
display_name=row["display_name"],
|
|
85
|
-
model_name=row["model_name"],
|
|
86
|
-
provider=row["provider"],
|
|
87
|
-
)
|
|
88
|
-
for _, row in df.iterrows()
|
|
89
|
-
]
|
|
90
|
-
except Exception:
|
|
91
|
-
pass
|
|
92
|
-
|
|
93
|
-
# 如果 ENV_NAME sheet 找不到或有問題,嘗試 default sheet
|
|
94
|
-
df = read_google_sheet(credentials_path, spreadsheet_id, "default")
|
|
95
|
-
if not df.empty and all(
|
|
96
|
-
col in df.columns for col in ["display_name", "model_name", "provider"]
|
|
97
|
-
):
|
|
98
|
-
try:
|
|
99
|
-
return [
|
|
100
|
-
ModelInfo(
|
|
101
|
-
display_name=row["display_name"],
|
|
102
|
-
model_name=row["model_name"],
|
|
103
|
-
provider=row["provider"],
|
|
104
|
-
)
|
|
105
|
-
for _, row in df.iterrows()
|
|
106
|
-
]
|
|
107
|
-
except Exception:
|
|
108
|
-
pass
|
|
109
|
-
|
|
110
|
-
# 如果都失敗,回退到 DEFAULT_SUPPORTED_MODELS
|
|
111
|
-
return DEFAULT_SUPPORTED_MODELS
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def get_agent_models_from_google_sheet() -> List[str]:
|
|
115
|
-
"""
|
|
116
|
-
從 Google Sheets 讀取 agent 模型列表
|
|
117
|
-
優先順序:ENV_NAME-agents sheet -> default-agents sheet -> DEFAULT_AGENT_MODELS
|
|
118
|
-
"""
|
|
119
|
-
credentials_path = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_FOR_MODELS_SHEET")
|
|
120
|
-
spreadsheet_id = os.getenv("MODELS_GSPREAD_ID")
|
|
121
|
-
env_name = os.getenv("ENV_NAME", "")
|
|
122
|
-
|
|
123
|
-
if not credentials_path or not spreadsheet_id:
|
|
124
|
-
return DEFAULT_AGENT_MODELS
|
|
125
|
-
|
|
126
|
-
# 首先嘗試使用 ENV_NAME-agents 作為 sheet_name
|
|
127
|
-
if env_name:
|
|
128
|
-
sheet_name = f"{env_name}-agents"
|
|
129
|
-
df = read_google_sheet(credentials_path, spreadsheet_id, sheet_name)
|
|
130
|
-
if not df.empty and "model_name" in df.columns:
|
|
131
|
-
try:
|
|
132
|
-
return [
|
|
133
|
-
row["model_name"] for _, row in df.iterrows() if row["model_name"]
|
|
134
|
-
]
|
|
135
|
-
except Exception:
|
|
136
|
-
pass
|
|
137
|
-
|
|
138
|
-
# 如果 ENV_NAME-agents sheet 找不到或有問題,嘗試 default-agents sheet
|
|
139
|
-
df = read_google_sheet(credentials_path, spreadsheet_id, "default-agents")
|
|
140
|
-
if not df.empty and "model_name" in df.columns:
|
|
141
|
-
try:
|
|
142
|
-
return [row["model_name"] for _, row in df.iterrows() if row["model_name"]]
|
|
143
|
-
except Exception:
|
|
144
|
-
pass
|
|
145
|
-
|
|
146
|
-
# 如果都失敗,回退到 DEFAULT_AGENT_MODELS
|
|
147
|
-
return DEFAULT_AGENT_MODELS
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
# 定義支援的 LLM models
|
|
151
|
-
DEFAULT_SUPPORTED_MODELS = [
|
|
152
|
-
ModelInfo(
|
|
153
|
-
display_name="gemini-2.5-flash",
|
|
154
|
-
model_name="gemini-2.5-flash",
|
|
155
|
-
provider="gemini",
|
|
156
|
-
),
|
|
157
|
-
ModelInfo(
|
|
158
|
-
display_name="claude-sonnet-4-5-20250929",
|
|
159
|
-
model_name="claude-sonnet-4-5-20250929",
|
|
160
|
-
provider="anthropic",
|
|
161
|
-
),
|
|
162
|
-
ModelInfo(
|
|
163
|
-
display_name="gemini-2.5-pro",
|
|
164
|
-
model_name="gemini-2.5-pro",
|
|
165
|
-
provider="gemini",
|
|
166
|
-
),
|
|
167
|
-
ModelInfo(
|
|
168
|
-
display_name="gpt-4.1-2025-04-14",
|
|
169
|
-
model_name="gpt-4.1-2025-04-14",
|
|
170
|
-
provider="openai",
|
|
171
|
-
),
|
|
172
|
-
ModelInfo(
|
|
173
|
-
display_name="o4-mini-2025-04-16",
|
|
174
|
-
model_name="o4-mini-2025-04-16",
|
|
175
|
-
provider="openai",
|
|
176
|
-
),
|
|
177
|
-
ModelInfo(
|
|
178
|
-
display_name="claude-3-5-haiku-latest",
|
|
179
|
-
model_name="claude-3-5-haiku-latest",
|
|
180
|
-
provider="anthropic",
|
|
181
|
-
),
|
|
182
|
-
ModelInfo(display_name="gpt-4o-mini", model_name="gpt-4o-mini", provider="openai"),
|
|
183
|
-
ModelInfo(
|
|
184
|
-
display_name="gpt-4o-2024-08-06",
|
|
185
|
-
model_name="gpt-4o-2024-08-06",
|
|
186
|
-
provider="openai",
|
|
187
|
-
),
|
|
188
|
-
ModelInfo(
|
|
189
|
-
display_name="Meta-Llama-3.1-8B-Instruct",
|
|
190
|
-
model_name="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
191
|
-
provider="deepinfra",
|
|
192
|
-
),
|
|
193
|
-
ModelInfo(
|
|
194
|
-
display_name="Meta-Llama-3.1-70B-Instruct-Turbo",
|
|
195
|
-
model_name="meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
|
|
196
|
-
provider="deepinfra",
|
|
197
|
-
),
|
|
198
|
-
ModelInfo(
|
|
199
|
-
display_name="Meta-Llama-3.1-405B-Instruct-Turbo",
|
|
200
|
-
model_name="meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
|
|
201
|
-
provider="together_ai",
|
|
202
|
-
),
|
|
203
|
-
ModelInfo(
|
|
204
|
-
display_name="Llama-3.1-Nemotron-70B-Instruct",
|
|
205
|
-
model_name="nvidia/Llama-3.1-Nemotron-70B-Instruct",
|
|
206
|
-
provider="deepinfra",
|
|
207
|
-
),
|
|
208
|
-
ModelInfo(
|
|
209
|
-
display_name="Llama-3.2-11B-Vision-Instruct-Turbo",
|
|
210
|
-
model_name="meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo",
|
|
211
|
-
provider="together_ai",
|
|
212
|
-
),
|
|
213
|
-
ModelInfo(
|
|
214
|
-
display_name="Llama-3.2-90B-Vision-Instruct-Turbo",
|
|
215
|
-
model_name="meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo",
|
|
216
|
-
provider="together_ai",
|
|
217
|
-
),
|
|
218
|
-
ModelInfo(
|
|
219
|
-
display_name="gemma-2-9b-it",
|
|
220
|
-
model_name="google/gemma-2-9b-it",
|
|
221
|
-
provider="deepinfra",
|
|
222
|
-
),
|
|
223
|
-
ModelInfo(
|
|
224
|
-
display_name="gemma-2-27b-it",
|
|
225
|
-
model_name="google/gemma-2-27b-it",
|
|
226
|
-
provider="deepinfra",
|
|
227
|
-
),
|
|
228
|
-
ModelInfo(
|
|
229
|
-
display_name="Mixtral-8x7B-Instruct-v0.1",
|
|
230
|
-
model_name="mistralai/Mixtral-8x7B-Instruct-v0.1",
|
|
231
|
-
provider="deepinfra",
|
|
232
|
-
),
|
|
233
|
-
ModelInfo(
|
|
234
|
-
display_name="Mixtral-8x22B-Instruct-v0.1",
|
|
235
|
-
model_name="mistralai/Mixtral-8x22B-Instruct-v0.1",
|
|
236
|
-
provider="deepinfra",
|
|
237
|
-
),
|
|
238
|
-
ModelInfo(
|
|
239
|
-
display_name="WizardLM-2-8x22B",
|
|
240
|
-
model_name="microsoft/WizardLM-2-8x22B",
|
|
241
|
-
provider="deepinfra",
|
|
242
|
-
),
|
|
243
|
-
ModelInfo(
|
|
244
|
-
display_name="Meta-Llama-Guard-3-8B",
|
|
245
|
-
model_name="meta-llama/Meta-Llama-Guard-3-8B",
|
|
246
|
-
provider="together_ai",
|
|
247
|
-
),
|
|
248
|
-
ModelInfo(display_name="o1-mini", model_name="o1-mini", provider="openai"),
|
|
249
|
-
ModelInfo(display_name="o1-preview", model_name="o1-preview", provider="openai"),
|
|
250
|
-
]
|
|
251
|
-
|
|
252
|
-
# 定義支援 agent 的 LLM models
|
|
253
|
-
DEFAULT_AGENT_MODELS = [
|
|
254
|
-
"claude-sonnet-4-5-20250929",
|
|
255
|
-
"gemini-2.5-pro",
|
|
256
|
-
"gemini-2.5-flash",
|
|
257
|
-
]
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
@router.get("/models", response_model=ModelsResponse)
|
|
261
|
-
async def list_models():
|
|
262
|
-
"""
|
|
263
|
-
列出所有支援的 LLM models
|
|
264
|
-
優先從 Google Sheets 讀取,回退到預設列表
|
|
265
|
-
|
|
266
|
-
Args:
|
|
267
|
-
provider: 可選的提供商篩選 (openai, anthropic, google, perplexity, etc.)
|
|
268
|
-
|
|
269
|
-
Returns:
|
|
270
|
-
ModelsResponse: 包含 models 清單和總數
|
|
271
|
-
"""
|
|
272
|
-
try:
|
|
273
|
-
# 從 Google Sheets 或預設列表獲取模型
|
|
274
|
-
all_models = get_models_from_google_sheet()
|
|
275
|
-
|
|
276
|
-
return ModelsResponse(models=all_models, total_count=len(all_models))
|
|
277
|
-
except Exception as e:
|
|
278
|
-
raise HTTPException(
|
|
279
|
-
status_code=500, detail=f"Failed to retrieve models: {str(e)}"
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
@router.get("/models/agent-models", response_model=AgentModelsResponse)
|
|
284
|
-
async def list_agent_models():
|
|
285
|
-
"""
|
|
286
|
-
列出所有支援的 agent models
|
|
287
|
-
優先從 Google Sheets 讀取,回退到預設列表
|
|
288
|
-
|
|
289
|
-
Returns:
|
|
290
|
-
AgentModelsResponse: 包含 models 清單和總數
|
|
291
|
-
"""
|
|
292
|
-
try:
|
|
293
|
-
# 從 Google Sheets 或預設列表獲取模型
|
|
294
|
-
all_models = get_agent_models_from_google_sheet()
|
|
295
|
-
|
|
296
|
-
return AgentModelsResponse(models=all_models, total_count=len(all_models))
|
|
297
|
-
except Exception as e:
|
|
298
|
-
raise HTTPException(
|
|
299
|
-
status_code=500, detail=f"Failed to retrieve agent models: {str(e)}"
|
|
300
|
-
)
|
|
1
|
+
import os
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from fastapi import APIRouter, HTTPException
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from google.oauth2 import service_account
|
|
7
|
+
from googleapiclient.discovery import build
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ModelInfo(BaseModel):
|
|
13
|
+
"""Model information structure"""
|
|
14
|
+
|
|
15
|
+
display_name: str
|
|
16
|
+
model_name: str
|
|
17
|
+
provider: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ModelsResponse(BaseModel):
|
|
21
|
+
"""Response model for listing supported models"""
|
|
22
|
+
|
|
23
|
+
models: List[ModelInfo]
|
|
24
|
+
total_count: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AgentModelsResponse(BaseModel):
|
|
28
|
+
"""Response model for listing supported agent models"""
|
|
29
|
+
|
|
30
|
+
models: List[str]
|
|
31
|
+
total_count: int
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def read_google_sheet(
|
|
35
|
+
credentials_path: str, spreadsheet_id: str, sheet_name: str
|
|
36
|
+
) -> pd.DataFrame:
|
|
37
|
+
"""
|
|
38
|
+
讀取 Google Spreadsheet 指定 sheet,回傳 pandas DataFrame。
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
|
|
42
|
+
credentials = service_account.Credentials.from_service_account_file(
|
|
43
|
+
credentials_path, scopes=scopes
|
|
44
|
+
)
|
|
45
|
+
service = build("sheets", "v4", credentials=credentials)
|
|
46
|
+
sheet = service.spreadsheets()
|
|
47
|
+
result = (
|
|
48
|
+
sheet.values().get(spreadsheetId=spreadsheet_id, range=sheet_name).execute()
|
|
49
|
+
)
|
|
50
|
+
values = result.get("values", [])
|
|
51
|
+
if not values:
|
|
52
|
+
return pd.DataFrame()
|
|
53
|
+
# 第一列為欄位名稱
|
|
54
|
+
df = pd.DataFrame(values[1:], columns=values[0])
|
|
55
|
+
return df
|
|
56
|
+
except Exception:
|
|
57
|
+
import traceback
|
|
58
|
+
|
|
59
|
+
traceback.print_exc()
|
|
60
|
+
return pd.DataFrame()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_models_from_google_sheet() -> List[ModelInfo]:
|
|
64
|
+
"""
|
|
65
|
+
從 Google Sheets 讀取模型列表
|
|
66
|
+
優先順序:ENV_NAME sheet -> default sheet -> DEFAULT_SUPPORTED_MODELS
|
|
67
|
+
"""
|
|
68
|
+
credentials_path = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_FOR_MODELS_SHEET")
|
|
69
|
+
spreadsheet_id = os.getenv("MODELS_GSPREAD_ID")
|
|
70
|
+
env_name = os.getenv("ENV_NAME", "")
|
|
71
|
+
|
|
72
|
+
if not credentials_path or not spreadsheet_id:
|
|
73
|
+
return DEFAULT_SUPPORTED_MODELS
|
|
74
|
+
|
|
75
|
+
# 首先嘗試使用 ENV_NAME 作為 sheet_name
|
|
76
|
+
if env_name:
|
|
77
|
+
df = read_google_sheet(credentials_path, spreadsheet_id, env_name)
|
|
78
|
+
if not df.empty and all(
|
|
79
|
+
col in df.columns for col in ["display_name", "model_name", "provider"]
|
|
80
|
+
):
|
|
81
|
+
try:
|
|
82
|
+
return [
|
|
83
|
+
ModelInfo(
|
|
84
|
+
display_name=row["display_name"],
|
|
85
|
+
model_name=row["model_name"],
|
|
86
|
+
provider=row["provider"],
|
|
87
|
+
)
|
|
88
|
+
for _, row in df.iterrows()
|
|
89
|
+
]
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
# 如果 ENV_NAME sheet 找不到或有問題,嘗試 default sheet
|
|
94
|
+
df = read_google_sheet(credentials_path, spreadsheet_id, "default")
|
|
95
|
+
if not df.empty and all(
|
|
96
|
+
col in df.columns for col in ["display_name", "model_name", "provider"]
|
|
97
|
+
):
|
|
98
|
+
try:
|
|
99
|
+
return [
|
|
100
|
+
ModelInfo(
|
|
101
|
+
display_name=row["display_name"],
|
|
102
|
+
model_name=row["model_name"],
|
|
103
|
+
provider=row["provider"],
|
|
104
|
+
)
|
|
105
|
+
for _, row in df.iterrows()
|
|
106
|
+
]
|
|
107
|
+
except Exception:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
# 如果都失敗,回退到 DEFAULT_SUPPORTED_MODELS
|
|
111
|
+
return DEFAULT_SUPPORTED_MODELS
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_agent_models_from_google_sheet() -> List[str]:
|
|
115
|
+
"""
|
|
116
|
+
從 Google Sheets 讀取 agent 模型列表
|
|
117
|
+
優先順序:ENV_NAME-agents sheet -> default-agents sheet -> DEFAULT_AGENT_MODELS
|
|
118
|
+
"""
|
|
119
|
+
credentials_path = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_FOR_MODELS_SHEET")
|
|
120
|
+
spreadsheet_id = os.getenv("MODELS_GSPREAD_ID")
|
|
121
|
+
env_name = os.getenv("ENV_NAME", "")
|
|
122
|
+
|
|
123
|
+
if not credentials_path or not spreadsheet_id:
|
|
124
|
+
return DEFAULT_AGENT_MODELS
|
|
125
|
+
|
|
126
|
+
# 首先嘗試使用 ENV_NAME-agents 作為 sheet_name
|
|
127
|
+
if env_name:
|
|
128
|
+
sheet_name = f"{env_name}-agents"
|
|
129
|
+
df = read_google_sheet(credentials_path, spreadsheet_id, sheet_name)
|
|
130
|
+
if not df.empty and "model_name" in df.columns:
|
|
131
|
+
try:
|
|
132
|
+
return [
|
|
133
|
+
row["model_name"] for _, row in df.iterrows() if row["model_name"]
|
|
134
|
+
]
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
# 如果 ENV_NAME-agents sheet 找不到或有問題,嘗試 default-agents sheet
|
|
139
|
+
df = read_google_sheet(credentials_path, spreadsheet_id, "default-agents")
|
|
140
|
+
if not df.empty and "model_name" in df.columns:
|
|
141
|
+
try:
|
|
142
|
+
return [row["model_name"] for _, row in df.iterrows() if row["model_name"]]
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
# 如果都失敗,回退到 DEFAULT_AGENT_MODELS
|
|
147
|
+
return DEFAULT_AGENT_MODELS
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# 定義支援的 LLM models
|
|
151
|
+
DEFAULT_SUPPORTED_MODELS = [
|
|
152
|
+
ModelInfo(
|
|
153
|
+
display_name="gemini-2.5-flash",
|
|
154
|
+
model_name="gemini-2.5-flash",
|
|
155
|
+
provider="gemini",
|
|
156
|
+
),
|
|
157
|
+
ModelInfo(
|
|
158
|
+
display_name="claude-sonnet-4-5-20250929",
|
|
159
|
+
model_name="claude-sonnet-4-5-20250929",
|
|
160
|
+
provider="anthropic",
|
|
161
|
+
),
|
|
162
|
+
ModelInfo(
|
|
163
|
+
display_name="gemini-2.5-pro",
|
|
164
|
+
model_name="gemini-2.5-pro",
|
|
165
|
+
provider="gemini",
|
|
166
|
+
),
|
|
167
|
+
ModelInfo(
|
|
168
|
+
display_name="gpt-4.1-2025-04-14",
|
|
169
|
+
model_name="gpt-4.1-2025-04-14",
|
|
170
|
+
provider="openai",
|
|
171
|
+
),
|
|
172
|
+
ModelInfo(
|
|
173
|
+
display_name="o4-mini-2025-04-16",
|
|
174
|
+
model_name="o4-mini-2025-04-16",
|
|
175
|
+
provider="openai",
|
|
176
|
+
),
|
|
177
|
+
ModelInfo(
|
|
178
|
+
display_name="claude-3-5-haiku-latest",
|
|
179
|
+
model_name="claude-3-5-haiku-latest",
|
|
180
|
+
provider="anthropic",
|
|
181
|
+
),
|
|
182
|
+
ModelInfo(display_name="gpt-4o-mini", model_name="gpt-4o-mini", provider="openai"),
|
|
183
|
+
ModelInfo(
|
|
184
|
+
display_name="gpt-4o-2024-08-06",
|
|
185
|
+
model_name="gpt-4o-2024-08-06",
|
|
186
|
+
provider="openai",
|
|
187
|
+
),
|
|
188
|
+
ModelInfo(
|
|
189
|
+
display_name="Meta-Llama-3.1-8B-Instruct",
|
|
190
|
+
model_name="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
191
|
+
provider="deepinfra",
|
|
192
|
+
),
|
|
193
|
+
ModelInfo(
|
|
194
|
+
display_name="Meta-Llama-3.1-70B-Instruct-Turbo",
|
|
195
|
+
model_name="meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
|
|
196
|
+
provider="deepinfra",
|
|
197
|
+
),
|
|
198
|
+
ModelInfo(
|
|
199
|
+
display_name="Meta-Llama-3.1-405B-Instruct-Turbo",
|
|
200
|
+
model_name="meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
|
|
201
|
+
provider="together_ai",
|
|
202
|
+
),
|
|
203
|
+
ModelInfo(
|
|
204
|
+
display_name="Llama-3.1-Nemotron-70B-Instruct",
|
|
205
|
+
model_name="nvidia/Llama-3.1-Nemotron-70B-Instruct",
|
|
206
|
+
provider="deepinfra",
|
|
207
|
+
),
|
|
208
|
+
ModelInfo(
|
|
209
|
+
display_name="Llama-3.2-11B-Vision-Instruct-Turbo",
|
|
210
|
+
model_name="meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo",
|
|
211
|
+
provider="together_ai",
|
|
212
|
+
),
|
|
213
|
+
ModelInfo(
|
|
214
|
+
display_name="Llama-3.2-90B-Vision-Instruct-Turbo",
|
|
215
|
+
model_name="meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo",
|
|
216
|
+
provider="together_ai",
|
|
217
|
+
),
|
|
218
|
+
ModelInfo(
|
|
219
|
+
display_name="gemma-2-9b-it",
|
|
220
|
+
model_name="google/gemma-2-9b-it",
|
|
221
|
+
provider="deepinfra",
|
|
222
|
+
),
|
|
223
|
+
ModelInfo(
|
|
224
|
+
display_name="gemma-2-27b-it",
|
|
225
|
+
model_name="google/gemma-2-27b-it",
|
|
226
|
+
provider="deepinfra",
|
|
227
|
+
),
|
|
228
|
+
ModelInfo(
|
|
229
|
+
display_name="Mixtral-8x7B-Instruct-v0.1",
|
|
230
|
+
model_name="mistralai/Mixtral-8x7B-Instruct-v0.1",
|
|
231
|
+
provider="deepinfra",
|
|
232
|
+
),
|
|
233
|
+
ModelInfo(
|
|
234
|
+
display_name="Mixtral-8x22B-Instruct-v0.1",
|
|
235
|
+
model_name="mistralai/Mixtral-8x22B-Instruct-v0.1",
|
|
236
|
+
provider="deepinfra",
|
|
237
|
+
),
|
|
238
|
+
ModelInfo(
|
|
239
|
+
display_name="WizardLM-2-8x22B",
|
|
240
|
+
model_name="microsoft/WizardLM-2-8x22B",
|
|
241
|
+
provider="deepinfra",
|
|
242
|
+
),
|
|
243
|
+
ModelInfo(
|
|
244
|
+
display_name="Meta-Llama-Guard-3-8B",
|
|
245
|
+
model_name="meta-llama/Meta-Llama-Guard-3-8B",
|
|
246
|
+
provider="together_ai",
|
|
247
|
+
),
|
|
248
|
+
ModelInfo(display_name="o1-mini", model_name="o1-mini", provider="openai"),
|
|
249
|
+
ModelInfo(display_name="o1-preview", model_name="o1-preview", provider="openai"),
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
# 定義支援 agent 的 LLM models
|
|
253
|
+
DEFAULT_AGENT_MODELS = [
|
|
254
|
+
"claude-sonnet-4-5-20250929",
|
|
255
|
+
"gemini-2.5-pro",
|
|
256
|
+
"gemini-2.5-flash",
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@router.get("/models", response_model=ModelsResponse)
|
|
261
|
+
async def list_models():
|
|
262
|
+
"""
|
|
263
|
+
列出所有支援的 LLM models
|
|
264
|
+
優先從 Google Sheets 讀取,回退到預設列表
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
provider: 可選的提供商篩選 (openai, anthropic, google, perplexity, etc.)
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
ModelsResponse: 包含 models 清單和總數
|
|
271
|
+
"""
|
|
272
|
+
try:
|
|
273
|
+
# 從 Google Sheets 或預設列表獲取模型
|
|
274
|
+
all_models = get_models_from_google_sheet()
|
|
275
|
+
|
|
276
|
+
return ModelsResponse(models=all_models, total_count=len(all_models))
|
|
277
|
+
except Exception as e:
|
|
278
|
+
raise HTTPException(
|
|
279
|
+
status_code=500, detail=f"Failed to retrieve models: {str(e)}"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@router.get("/models/agent-models", response_model=AgentModelsResponse)
|
|
284
|
+
async def list_agent_models():
|
|
285
|
+
"""
|
|
286
|
+
列出所有支援的 agent models
|
|
287
|
+
優先從 Google Sheets 讀取,回退到預設列表
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
AgentModelsResponse: 包含 models 清單和總數
|
|
291
|
+
"""
|
|
292
|
+
try:
|
|
293
|
+
# 從 Google Sheets 或預設列表獲取模型
|
|
294
|
+
all_models = get_agent_models_from_google_sheet()
|
|
295
|
+
|
|
296
|
+
return AgentModelsResponse(models=all_models, total_count=len(all_models))
|
|
297
|
+
except Exception as e:
|
|
298
|
+
raise HTTPException(
|
|
299
|
+
status_code=500, detail=f"Failed to retrieve agent models: {str(e)}"
|
|
300
|
+
)
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
from fastapi import APIRouter, HTTPException
|
|
2
|
-
from typing import Dict, Any
|
|
3
|
-
|
|
4
|
-
from botrun_flow_lang.utils.clients.rate_limit_client import RateLimitClient
|
|
5
|
-
|
|
6
|
-
router = APIRouter(prefix="/rate_limit")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@router.get("/{username}")
|
|
10
|
-
async def get_user_rate_limit(username: str) -> Dict[Any, Any]:
|
|
11
|
-
"""
|
|
12
|
-
獲取指定用戶的 rate limit 信息。
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
username: 用戶名
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
包含用戶 rate limit 信息的字典
|
|
19
|
-
|
|
20
|
-
Raises:
|
|
21
|
-
HTTPException: 當用戶不存在或後端 API 無法訪問時
|
|
22
|
-
"""
|
|
23
|
-
try:
|
|
24
|
-
client = RateLimitClient()
|
|
25
|
-
result = await client.get_rate_limit(username)
|
|
26
|
-
return result
|
|
27
|
-
except ValueError as e:
|
|
28
|
-
# 使用者不存在時會回傳 404
|
|
29
|
-
if "User not found" in str(e):
|
|
30
|
-
raise HTTPException(status_code=404, detail=f"User not found: {username}")
|
|
31
|
-
# 其他錯誤回傳 500
|
|
32
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
|
|
4
|
+
from botrun_flow_lang.utils.clients.rate_limit_client import RateLimitClient
|
|
5
|
+
|
|
6
|
+
router = APIRouter(prefix="/rate_limit")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@router.get("/{username}")
|
|
10
|
+
async def get_user_rate_limit(username: str) -> Dict[Any, Any]:
|
|
11
|
+
"""
|
|
12
|
+
獲取指定用戶的 rate limit 信息。
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
username: 用戶名
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
包含用戶 rate limit 信息的字典
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
HTTPException: 當用戶不存在或後端 API 無法訪問時
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
client = RateLimitClient()
|
|
25
|
+
result = await client.get_rate_limit(username)
|
|
26
|
+
return result
|
|
27
|
+
except ValueError as e:
|
|
28
|
+
# 使用者不存在時會回傳 404
|
|
29
|
+
if "User not found" in str(e):
|
|
30
|
+
raise HTTPException(status_code=404, detail=f"User not found: {username}")
|
|
31
|
+
# 其他錯誤回傳 500
|
|
32
|
+
raise HTTPException(status_code=500, detail=str(e))
|