botrun-flow-lang 5.9.301__py3-none-any.whl → 5.10.82__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. botrun_flow_lang/api/auth_api.py +39 -39
  2. botrun_flow_lang/api/auth_utils.py +183 -183
  3. botrun_flow_lang/api/botrun_back_api.py +65 -65
  4. botrun_flow_lang/api/flow_api.py +3 -3
  5. botrun_flow_lang/api/hatch_api.py +481 -481
  6. botrun_flow_lang/api/langgraph_api.py +796 -796
  7. botrun_flow_lang/api/line_bot_api.py +1357 -1357
  8. botrun_flow_lang/api/model_api.py +300 -300
  9. botrun_flow_lang/api/rate_limit_api.py +32 -32
  10. botrun_flow_lang/api/routes.py +79 -79
  11. botrun_flow_lang/api/search_api.py +53 -53
  12. botrun_flow_lang/api/storage_api.py +316 -316
  13. botrun_flow_lang/api/subsidy_api.py +290 -290
  14. botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
  15. botrun_flow_lang/api/user_setting_api.py +70 -70
  16. botrun_flow_lang/api/version_api.py +31 -31
  17. botrun_flow_lang/api/youtube_api.py +26 -26
  18. botrun_flow_lang/constants.py +13 -13
  19. botrun_flow_lang/langgraph_agents/agents/agent_runner.py +174 -174
  20. botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
  21. botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
  22. botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
  23. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
  24. botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
  25. botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +548 -542
  26. botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
  27. botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
  28. botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
  29. botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
  30. botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
  31. botrun_flow_lang/langgraph_agents/agents/util/img_util.py +294 -294
  32. botrun_flow_lang/langgraph_agents/agents/util/local_files.py +345 -345
  33. botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
  34. botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
  35. botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +160 -160
  36. botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
  37. botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
  38. botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
  39. botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
  40. botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
  41. botrun_flow_lang/llm_agent/llm_agent.py +19 -19
  42. botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
  43. botrun_flow_lang/log/.gitignore +2 -2
  44. botrun_flow_lang/main.py +61 -61
  45. botrun_flow_lang/main_fast.py +51 -51
  46. botrun_flow_lang/mcp_server/__init__.py +10 -10
  47. botrun_flow_lang/mcp_server/default_mcp.py +711 -711
  48. botrun_flow_lang/models/nodes/utils.py +205 -205
  49. botrun_flow_lang/models/token_usage.py +34 -34
  50. botrun_flow_lang/requirements.txt +21 -21
  51. botrun_flow_lang/services/base/firestore_base.py +30 -30
  52. botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
  53. botrun_flow_lang/services/hatch/hatch_fs_store.py +372 -372
  54. botrun_flow_lang/services/storage/storage_cs_store.py +202 -202
  55. botrun_flow_lang/services/storage/storage_factory.py +12 -12
  56. botrun_flow_lang/services/storage/storage_store.py +65 -65
  57. botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
  58. botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
  59. botrun_flow_lang/static/docs/tools/index.html +926 -926
  60. botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
  61. botrun_flow_lang/tests/api_stress_test.py +357 -357
  62. botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
  63. botrun_flow_lang/tests/test_botrun_app.py +46 -46
  64. botrun_flow_lang/tests/test_html_util.py +31 -31
  65. botrun_flow_lang/tests/test_img_analyzer.py +190 -190
  66. botrun_flow_lang/tests/test_img_util.py +39 -39
  67. botrun_flow_lang/tests/test_local_files.py +114 -114
  68. botrun_flow_lang/tests/test_mermaid_util.py +103 -103
  69. botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
  70. botrun_flow_lang/tests/test_plotly_util.py +151 -151
  71. botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
  72. botrun_flow_lang/tools/generate_docs.py +133 -133
  73. botrun_flow_lang/tools/templates/tools.html +153 -153
  74. botrun_flow_lang/utils/__init__.py +7 -7
  75. botrun_flow_lang/utils/botrun_logger.py +344 -344
  76. botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
  77. botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
  78. botrun_flow_lang/utils/google_drive_utils.py +654 -654
  79. botrun_flow_lang/utils/langchain_utils.py +324 -324
  80. botrun_flow_lang/utils/yaml_utils.py +9 -9
  81. {botrun_flow_lang-5.9.301.dist-info → botrun_flow_lang-5.10.82.dist-info}/METADATA +2 -2
  82. botrun_flow_lang-5.10.82.dist-info/RECORD +99 -0
  83. botrun_flow_lang-5.9.301.dist-info/RECORD +0 -99
  84. {botrun_flow_lang-5.9.301.dist-info → botrun_flow_lang-5.10.82.dist-info}/WHEEL +0 -0
@@ -1,290 +1,290 @@
1
- from fastapi import APIRouter, HTTPException
2
- from fastapi.responses import StreamingResponse
3
- from typing import List, Dict, Union, AsyncIterator, Optional
4
- import os
5
- import uuid
6
-
7
- from pydantic import BaseModel
8
- import time
9
- import json
10
- from pathlib import Path
11
- from botrun_flow_lang.api.line_bot_api import (
12
- get_subsidy_api_system_prompt,
13
- get_subsidy_bot_normal_chat_prompt,
14
- get_subsidy_bot_related_prompt,
15
- get_subsidy_bot_requirement_prompt,
16
- )
17
- from botrun_flow_lang.langgraph_agents.agents.util.perplexity_search import (
18
- respond_with_perplexity_search,
19
- )
20
- from botrun_flow_lang.langgraph_agents.agents.agent_runner import agent_runner
21
- from botrun_flow_lang.langgraph_agents.agents.search_agent_graph import (
22
- SearchAgentGraph,
23
- DEFAULT_SEARCH_CONFIG,
24
- )
25
- from fastapi import HTTPException, Depends
26
-
27
- from dotenv import load_dotenv
28
-
29
- from botrun_flow_lang.utils.langchain_utils import litellm_msgs_to_langchain_msgs
30
- from botrun_flow_lang.api.auth_utils import verify_token
31
-
32
- load_dotenv()
33
-
34
- router = APIRouter(prefix="/subsidy")
35
-
36
-
37
- # 自定義 Pydantic 模型替換 litellm 類型
38
- class Delta(BaseModel):
39
- """Delta represents a change in the message content."""
40
-
41
- content: Optional[str] = None
42
- role: Optional[str] = None
43
-
44
-
45
- class Message(BaseModel):
46
- """Message represents a chat message."""
47
-
48
- content: str
49
- role: str = "assistant"
50
-
51
-
52
- class Choices(BaseModel):
53
- """Choices represents a set of alternatives in the API response."""
54
-
55
- index: int
56
- delta: Optional[Delta] = None
57
- message: Optional[Message] = None
58
- finish_reason: Optional[str] = None
59
-
60
-
61
- # 讀取系統提示詞文件
62
- # current_dir = Path(__file__).parent
63
- # DEFAULT_SYSTEM_PROMPT = (current_dir / "subsidy_api_system_prompt.txt").read_text(
64
- # encoding="utf-8"
65
- # )
66
-
67
- # 建立 subsidy_api 專用的 SearchAgentGraph 實例
68
- subsidy_api_graph = SearchAgentGraph().graph
69
-
70
-
71
- class SubsidyCompletionRequest(BaseModel):
72
- messages: List[Dict]
73
- stream: bool = False
74
- system_prompt_roy: Optional[str] = None # 新增這行
75
-
76
-
77
- class SubsidyCompletionResponse(BaseModel):
78
- """
79
- Non-streaming response format for completion endpoint
80
- """
81
-
82
- id: str
83
- object: str = "chat.completion"
84
- created: int
85
- choices: List[Choices] = []
86
- state: Dict = {}
87
-
88
-
89
- class SubsidyCompletionStreamChunk(BaseModel):
90
- """
91
- Streaming response chunk format for completion endpoint
92
- """
93
-
94
- id: str
95
- object: str = "chat.completion.chunk"
96
- created: int
97
- choices: List[Choices] = []
98
- state: Dict = {}
99
-
100
-
101
- def validate_messages(messages: List[Dict]) -> str:
102
- """
103
- Validate messages and extract the last user message content
104
-
105
- Args:
106
- messages: List of message dictionaries
107
-
108
- Returns:
109
- The content of the last user message
110
-
111
- Raises:
112
- HTTPException: If the last message is not from user
113
- """
114
- if not messages or messages[-1].get("role") != "user":
115
- raise HTTPException(
116
- status_code=400, detail="The last message must have role 'user'"
117
- )
118
- return messages[-1].get("content", "")
119
-
120
-
121
- def get_subsidy_search_config(stream: bool = True) -> dict:
122
- return {
123
- **DEFAULT_SEARCH_CONFIG,
124
- "requirement_prompt": get_subsidy_bot_requirement_prompt(),
125
- "search_prompt": get_subsidy_api_system_prompt(),
126
- "related_prompt": get_subsidy_bot_related_prompt(),
127
- "normal_chat_prompt": get_subsidy_bot_normal_chat_prompt(),
128
- "domain_filter": ["*.gov.tw", "-*.gov.cn"],
129
- "user_prompt_prefix": "你是台灣人,你不可以講中國用語也不可以用簡體中文,禁止!",
130
- "stream": stream,
131
- }
132
-
133
-
134
- async def process_stream_response(messages: List[Dict]) -> AsyncIterator[str]:
135
- """
136
- Process streaming response from perplexity search
137
-
138
- Args:
139
- messages: List of message dictionaries
140
-
141
- Yields:
142
- SSE formatted string chunks
143
- """
144
- input_content = validate_messages(messages)
145
- messages_for_langchain = litellm_msgs_to_langchain_msgs(messages)
146
- messages_for_llm = messages[:-1]
147
- env_name = os.getenv("ENV_NAME")
148
- thread_id = str(uuid.uuid4())
149
- async for event in agent_runner(
150
- thread_id,
151
- {"messages": messages_for_langchain},
152
- subsidy_api_graph,
153
- extra_config=get_subsidy_search_config(),
154
- ):
155
- chunk_content = event.chunk
156
- choice = Choices(
157
- index=0,
158
- delta=Delta(content=chunk_content),
159
- finish_reason=None,
160
- )
161
- id = f"{env_name}-{uuid.uuid4()}"
162
- stream_chunk = SubsidyCompletionStreamChunk(
163
- id=id,
164
- created=int(time.time()),
165
- choices=[choice],
166
- )
167
- yield f"data: {json.dumps(stream_chunk.model_dump(), ensure_ascii=False)}\n\n"
168
- choice = Choices(
169
- index=0,
170
- delta=Delta(content=""),
171
- finish_reason=None,
172
- )
173
- id = f"{env_name}-{uuid.uuid4()}"
174
- state = subsidy_api_graph.get_state({"configurable": {"thread_id": thread_id}})
175
- related_questions = state.values.get("related_questions", [])
176
- stream_chunk = SubsidyCompletionStreamChunk(
177
- id=id,
178
- created=int(time.time()),
179
- choices=[choice],
180
- state={"related_questions": related_questions},
181
- )
182
- yield f"data: {json.dumps(stream_chunk.model_dump(), ensure_ascii=False)}\n\n"
183
-
184
- yield "data: [DONE]\n\n"
185
-
186
-
187
- async def process_non_stream_response(
188
- messages: List[Dict],
189
- ) -> SubsidyCompletionResponse:
190
- """
191
- Process non-streaming response from perplexity search
192
-
193
- Args:
194
- messages: List of message dictionaries
195
-
196
- Returns:
197
- SubsidyCompletionResponse with the complete response
198
- """
199
- input_content = validate_messages(messages)
200
- messages_for_langchain = litellm_msgs_to_langchain_msgs(messages)
201
- messages_for_llm = messages[:-1]
202
-
203
- full_content = ""
204
- # async for event in respond_with_perplexity_search(
205
- # input_content=input_content,
206
- # user_prompt_prefix="",
207
- # messages_for_llm=messages_for_llm,
208
- # domain_filter=["*.gov.tw", "-*.gov.cn"],
209
- # ):
210
- thread_id = str(uuid.uuid4())
211
-
212
- print(f"[subsidy_api: process_non_stream_response()] start")
213
- t1 = time.time()
214
- async for event in agent_runner(
215
- thread_id,
216
- {"messages": messages_for_langchain},
217
- subsidy_api_graph,
218
- extra_config=get_subsidy_search_config(stream=False),
219
- ):
220
- full_content += event.chunk
221
- print(f"[subsidy_api: process_non_stream_response()] end")
222
- t2 = time.time()
223
- print(f"[subsidy_api: process_non_stream_response()] took {t2-t1}")
224
-
225
- choice = Choices(
226
- index=0,
227
- message=Message(content=full_content),
228
- finish_reason="stop",
229
- )
230
- env_name = os.getenv("ENV_NAME")
231
- id = f"{env_name}-{uuid.uuid4()}"
232
- state = subsidy_api_graph.get_state({"configurable": {"thread_id": thread_id}})
233
- related_questions = state.values.get("related_questions", [])
234
-
235
- return SubsidyCompletionResponse(
236
- id=id,
237
- created=int(time.time()),
238
- choices=[choice],
239
- state={"related_questions": related_questions},
240
- )
241
-
242
-
243
- def process_messages(
244
- messages: List[Dict], system_prompt_roy: Optional[str] = None
245
- ) -> List[Dict]:
246
- # Remove any existing system messages
247
- return [msg for msg in messages if msg.get("role") != "system"]
248
-
249
-
250
- @router.post("/completion", dependencies=[Depends(verify_token)])
251
- async def completion(
252
- request: SubsidyCompletionRequest,
253
- ):
254
- """
255
- Generates a text completion using perplexity search.
256
-
257
- Args:
258
- request: CompletionRequest containing messages and stream flag
259
-
260
- Returns:
261
- If stream is False, returns a CompletionResponse
262
- If stream is True, returns a StreamingResponse with SSE format
263
- """
264
- try:
265
- processed_messages = process_messages(
266
- request.messages, request.system_prompt_roy
267
- )
268
- # system_prompt = (
269
- # request.system_prompt_roy
270
- # if request.system_prompt_roy is not None
271
- # else DEFAULT_SYSTEM_PROMPT
272
- # )
273
- # system_prompt = get_subsidy_api_system_prompt()
274
- if request.stream:
275
- return StreamingResponse(
276
- process_stream_response(processed_messages),
277
- media_type="text/event-stream",
278
- headers={
279
- "Cache-Control": "no-cache",
280
- "Connection": "keep-alive",
281
- },
282
- )
283
- else:
284
- return await process_non_stream_response(processed_messages)
285
-
286
- except Exception as e:
287
- import traceback
288
-
289
- traceback.print_exc()
290
- raise HTTPException(status_code=500, detail=str(e))
1
+ from fastapi import APIRouter, HTTPException
2
+ from fastapi.responses import StreamingResponse
3
+ from typing import List, Dict, Union, AsyncIterator, Optional
4
+ import os
5
+ import uuid
6
+
7
+ from pydantic import BaseModel
8
+ import time
9
+ import json
10
+ from pathlib import Path
11
+ from botrun_flow_lang.api.line_bot_api import (
12
+ get_subsidy_api_system_prompt,
13
+ get_subsidy_bot_normal_chat_prompt,
14
+ get_subsidy_bot_related_prompt,
15
+ get_subsidy_bot_requirement_prompt,
16
+ )
17
+ from botrun_flow_lang.langgraph_agents.agents.util.perplexity_search import (
18
+ respond_with_perplexity_search,
19
+ )
20
+ from botrun_flow_lang.langgraph_agents.agents.agent_runner import agent_runner
21
+ from botrun_flow_lang.langgraph_agents.agents.search_agent_graph import (
22
+ SearchAgentGraph,
23
+ DEFAULT_SEARCH_CONFIG,
24
+ )
25
+ from fastapi import HTTPException, Depends
26
+
27
+ from dotenv import load_dotenv
28
+
29
+ from botrun_flow_lang.utils.langchain_utils import litellm_msgs_to_langchain_msgs
30
+ from botrun_flow_lang.api.auth_utils import verify_token
31
+
32
+ load_dotenv()
33
+
34
+ router = APIRouter(prefix="/subsidy")
35
+
36
+
37
+ # 自定義 Pydantic 模型替換 litellm 類型
38
+ class Delta(BaseModel):
39
+ """Delta represents a change in the message content."""
40
+
41
+ content: Optional[str] = None
42
+ role: Optional[str] = None
43
+
44
+
45
+ class Message(BaseModel):
46
+ """Message represents a chat message."""
47
+
48
+ content: str
49
+ role: str = "assistant"
50
+
51
+
52
+ class Choices(BaseModel):
53
+ """Choices represents a set of alternatives in the API response."""
54
+
55
+ index: int
56
+ delta: Optional[Delta] = None
57
+ message: Optional[Message] = None
58
+ finish_reason: Optional[str] = None
59
+
60
+
61
+ # 讀取系統提示詞文件
62
+ # current_dir = Path(__file__).parent
63
+ # DEFAULT_SYSTEM_PROMPT = (current_dir / "subsidy_api_system_prompt.txt").read_text(
64
+ # encoding="utf-8"
65
+ # )
66
+
67
+ # 建立 subsidy_api 專用的 SearchAgentGraph 實例
68
+ subsidy_api_graph = SearchAgentGraph().graph
69
+
70
+
71
+ class SubsidyCompletionRequest(BaseModel):
72
+ messages: List[Dict]
73
+ stream: bool = False
74
+ system_prompt_roy: Optional[str] = None # 新增這行
75
+
76
+
77
+ class SubsidyCompletionResponse(BaseModel):
78
+ """
79
+ Non-streaming response format for completion endpoint
80
+ """
81
+
82
+ id: str
83
+ object: str = "chat.completion"
84
+ created: int
85
+ choices: List[Choices] = []
86
+ state: Dict = {}
87
+
88
+
89
+ class SubsidyCompletionStreamChunk(BaseModel):
90
+ """
91
+ Streaming response chunk format for completion endpoint
92
+ """
93
+
94
+ id: str
95
+ object: str = "chat.completion.chunk"
96
+ created: int
97
+ choices: List[Choices] = []
98
+ state: Dict = {}
99
+
100
+
101
+ def validate_messages(messages: List[Dict]) -> str:
102
+ """
103
+ Validate messages and extract the last user message content
104
+
105
+ Args:
106
+ messages: List of message dictionaries
107
+
108
+ Returns:
109
+ The content of the last user message
110
+
111
+ Raises:
112
+ HTTPException: If the last message is not from user
113
+ """
114
+ if not messages or messages[-1].get("role") != "user":
115
+ raise HTTPException(
116
+ status_code=400, detail="The last message must have role 'user'"
117
+ )
118
+ return messages[-1].get("content", "")
119
+
120
+
121
+ def get_subsidy_search_config(stream: bool = True) -> dict:
122
+ return {
123
+ **DEFAULT_SEARCH_CONFIG,
124
+ "requirement_prompt": get_subsidy_bot_requirement_prompt(),
125
+ "search_prompt": get_subsidy_api_system_prompt(),
126
+ "related_prompt": get_subsidy_bot_related_prompt(),
127
+ "normal_chat_prompt": get_subsidy_bot_normal_chat_prompt(),
128
+ "domain_filter": ["*.gov.tw", "-*.gov.cn"],
129
+ "user_prompt_prefix": "你是台灣人,你不可以講中國用語也不可以用簡體中文,禁止!",
130
+ "stream": stream,
131
+ }
132
+
133
+
134
+ async def process_stream_response(messages: List[Dict]) -> AsyncIterator[str]:
135
+ """
136
+ Process streaming response from perplexity search
137
+
138
+ Args:
139
+ messages: List of message dictionaries
140
+
141
+ Yields:
142
+ SSE formatted string chunks
143
+ """
144
+ input_content = validate_messages(messages)
145
+ messages_for_langchain = litellm_msgs_to_langchain_msgs(messages)
146
+ messages_for_llm = messages[:-1]
147
+ env_name = os.getenv("ENV_NAME")
148
+ thread_id = str(uuid.uuid4())
149
+ async for event in agent_runner(
150
+ thread_id,
151
+ {"messages": messages_for_langchain},
152
+ subsidy_api_graph,
153
+ extra_config=get_subsidy_search_config(),
154
+ ):
155
+ chunk_content = event.chunk
156
+ choice = Choices(
157
+ index=0,
158
+ delta=Delta(content=chunk_content),
159
+ finish_reason=None,
160
+ )
161
+ id = f"{env_name}-{uuid.uuid4()}"
162
+ stream_chunk = SubsidyCompletionStreamChunk(
163
+ id=id,
164
+ created=int(time.time()),
165
+ choices=[choice],
166
+ )
167
+ yield f"data: {json.dumps(stream_chunk.model_dump(), ensure_ascii=False)}\n\n"
168
+ choice = Choices(
169
+ index=0,
170
+ delta=Delta(content=""),
171
+ finish_reason=None,
172
+ )
173
+ id = f"{env_name}-{uuid.uuid4()}"
174
+ state = subsidy_api_graph.get_state({"configurable": {"thread_id": thread_id}})
175
+ related_questions = state.values.get("related_questions", [])
176
+ stream_chunk = SubsidyCompletionStreamChunk(
177
+ id=id,
178
+ created=int(time.time()),
179
+ choices=[choice],
180
+ state={"related_questions": related_questions},
181
+ )
182
+ yield f"data: {json.dumps(stream_chunk.model_dump(), ensure_ascii=False)}\n\n"
183
+
184
+ yield "data: [DONE]\n\n"
185
+
186
+
187
+ async def process_non_stream_response(
188
+ messages: List[Dict],
189
+ ) -> SubsidyCompletionResponse:
190
+ """
191
+ Process non-streaming response from perplexity search
192
+
193
+ Args:
194
+ messages: List of message dictionaries
195
+
196
+ Returns:
197
+ SubsidyCompletionResponse with the complete response
198
+ """
199
+ input_content = validate_messages(messages)
200
+ messages_for_langchain = litellm_msgs_to_langchain_msgs(messages)
201
+ messages_for_llm = messages[:-1]
202
+
203
+ full_content = ""
204
+ # async for event in respond_with_perplexity_search(
205
+ # input_content=input_content,
206
+ # user_prompt_prefix="",
207
+ # messages_for_llm=messages_for_llm,
208
+ # domain_filter=["*.gov.tw", "-*.gov.cn"],
209
+ # ):
210
+ thread_id = str(uuid.uuid4())
211
+
212
+ print(f"[subsidy_api: process_non_stream_response()] start")
213
+ t1 = time.time()
214
+ async for event in agent_runner(
215
+ thread_id,
216
+ {"messages": messages_for_langchain},
217
+ subsidy_api_graph,
218
+ extra_config=get_subsidy_search_config(stream=False),
219
+ ):
220
+ full_content += event.chunk
221
+ print(f"[subsidy_api: process_non_stream_response()] end")
222
+ t2 = time.time()
223
+ print(f"[subsidy_api: process_non_stream_response()] took {t2-t1}")
224
+
225
+ choice = Choices(
226
+ index=0,
227
+ message=Message(content=full_content),
228
+ finish_reason="stop",
229
+ )
230
+ env_name = os.getenv("ENV_NAME")
231
+ id = f"{env_name}-{uuid.uuid4()}"
232
+ state = subsidy_api_graph.get_state({"configurable": {"thread_id": thread_id}})
233
+ related_questions = state.values.get("related_questions", [])
234
+
235
+ return SubsidyCompletionResponse(
236
+ id=id,
237
+ created=int(time.time()),
238
+ choices=[choice],
239
+ state={"related_questions": related_questions},
240
+ )
241
+
242
+
243
+ def process_messages(
244
+ messages: List[Dict], system_prompt_roy: Optional[str] = None
245
+ ) -> List[Dict]:
246
+ # Remove any existing system messages
247
+ return [msg for msg in messages if msg.get("role") != "system"]
248
+
249
+
250
+ @router.post("/completion", dependencies=[Depends(verify_token)])
251
+ async def completion(
252
+ request: SubsidyCompletionRequest,
253
+ ):
254
+ """
255
+ Generates a text completion using perplexity search.
256
+
257
+ Args:
258
+ request: CompletionRequest containing messages and stream flag
259
+
260
+ Returns:
261
+ If stream is False, returns a CompletionResponse
262
+ If stream is True, returns a StreamingResponse with SSE format
263
+ """
264
+ try:
265
+ processed_messages = process_messages(
266
+ request.messages, request.system_prompt_roy
267
+ )
268
+ # system_prompt = (
269
+ # request.system_prompt_roy
270
+ # if request.system_prompt_roy is not None
271
+ # else DEFAULT_SYSTEM_PROMPT
272
+ # )
273
+ # system_prompt = get_subsidy_api_system_prompt()
274
+ if request.stream:
275
+ return StreamingResponse(
276
+ process_stream_response(processed_messages),
277
+ media_type="text/event-stream",
278
+ headers={
279
+ "Cache-Control": "no-cache",
280
+ "Connection": "keep-alive",
281
+ },
282
+ )
283
+ else:
284
+ return await process_non_stream_response(processed_messages)
285
+
286
+ except Exception as e:
287
+ import traceback
288
+
289
+ traceback.print_exc()
290
+ raise HTTPException(status_code=500, detail=str(e))