T7yrttrd commited on
Commit
98e23ae
·
verified ·
1 Parent(s): ad7cac4

Delete service_chat.py

Browse files
Files changed (1) hide show
  1. service_chat.py +0 -343
service_chat.py DELETED
@@ -1,343 +0,0 @@
1
- import json
2
- import asyncio
3
- import re
4
- import tiktoken
5
- from config import BAI_API_KEY, BAI_URL, MODEL_NAME
6
- from tools_def import get_filtered_tools
7
- from service_connectors import execute_tool_call, execute_tavily_search
8
-
9
- enc = tiktoken.get_encoding("cl100k_base")
10
-
11
- def count_tokens(messages):
12
- num_tokens = 0
13
- for message in messages:
14
- num_tokens += 4
15
- for key, value in message.items():
16
- if isinstance(value, str):
17
- num_tokens += len(enc.encode(value))
18
- elif isinstance(value, list):
19
- for item in value:
20
- if isinstance(item, dict):
21
- if item.get("type") == "text":
22
- num_tokens += len(enc.encode(item.get("text", "")))
23
- elif item.get("type") == "image_url":
24
- num_tokens += 200
25
- num_tokens += 2
26
- return num_tokens
27
-
28
- def normalize_query_for_cache(q: str) -> str:
29
- cleaned = re.sub(r"[^\w\s]", "", q.lower()).strip()
30
- return re.sub(r"\s+", " ", cleaned)
31
-
32
- def format_image_url(img_str):
33
- if not isinstance(img_str, str) or not img_str.strip():
34
- return None
35
- img_clean = img_str.strip()
36
- if img_clean.startswith("http://") or img_clean.startswith("https://") or img_clean.startswith("data:"):
37
- return img_clean
38
- return f"data:image/jpeg;base64,{img_clean}"
39
-
40
- def normalize_content(content):
41
- if isinstance(content, str):
42
- return content.strip()
43
- if isinstance(content, list):
44
- normalized = []
45
- for item in content:
46
- if isinstance(item, dict):
47
- t = item.get("type")
48
- if t == "text" and item.get("text"):
49
- normalized.append({"type": "text", "text": str(item.get("text", "")).strip()})
50
- elif t == "image_url":
51
- url = item.get("image_url", {}).get("url", "")
52
- fmt_url = format_image_url(url)
53
- if fmt_url:
54
- normalized.append({"type": "image_url", "image_url": {"url": fmt_url}})
55
- elif "image" in item:
56
- fmt_url = format_image_url(item.get("image", ""))
57
- if fmt_url:
58
- normalized.append({"type": "image_url", "image_url": {"url": fmt_url}})
59
- return normalized
60
- return ""
61
-
62
- def build_context_messages(req_messages, user_prompt, images=None, max_tokens=100000):
63
- raw_list = []
64
-
65
- if req_messages and isinstance(req_messages, list) and len(req_messages) > 0:
66
- raw_list = req_messages
67
- elif user_prompt or images:
68
- content_parts = []
69
- if user_prompt:
70
- content_parts.append({"type": "text", "text": user_prompt})
71
- if images and isinstance(images, list):
72
- for img in images:
73
- fmt_url = format_image_url(img)
74
- if fmt_url:
75
- content_parts.append({
76
- "type": "image_url",
77
- "image_url": {"url": fmt_url}
78
- })
79
- if len(content_parts) == 1 and content_parts[0]["type"] == "text":
80
- raw_list = [{"role": "user", "content": content_parts[0]["text"]}]
81
- else:
82
- raw_list = [{"role": "user", "content": content_parts}]
83
-
84
- cleaned = []
85
- for msg in raw_list:
86
- if not isinstance(msg, dict):
87
- continue
88
- role = msg.get("role")
89
- content = normalize_content(msg.get("content"))
90
- if not role or not content:
91
- continue
92
- cleaned.append({"role": role, "content": content})
93
-
94
- if images and isinstance(images, list) and len(images) > 0 and cleaned:
95
- last_msg = cleaned[-1]
96
- if last_msg.get("role") == "user":
97
- current_content = last_msg.get("content")
98
- if isinstance(current_content, str):
99
- content_parts = [{"type": "text", "text": current_content}] if current_content else []
100
- elif isinstance(current_content, list):
101
- content_parts = list(current_content)
102
- else:
103
- content_parts = []
104
-
105
- for img in images:
106
- fmt_url = format_image_url(img)
107
- if fmt_url and not any(p.get("type") == "image_url" and p.get("image_url", {}).get("url") == fmt_url for p in content_parts):
108
- content_parts.append({
109
- "type": "image_url",
110
- "image_url": {"url": fmt_url}
111
- })
112
- last_msg["content"] = content_parts
113
-
114
- while cleaned and count_tokens(cleaned) > max_tokens:
115
- cleaned.pop(0)
116
- while cleaned and cleaned[0]["role"] != "user":
117
- cleaned.pop(0)
118
-
119
- return cleaned
120
-
121
- def get_tool_metadata(func_name: str, args: dict):
122
- if func_name == "tavily_search":
123
- q = args.get("query", "the web")
124
- depth = args.get("search_depth", "basic")
125
- if depth == "advanced":
126
- return "search", f"Deep Research: {q}"
127
- return "search", f"Searching: {q}"
128
-
129
- endpoint = args.get("endpoint", "")
130
- method = args.get("method", "GET")
131
-
132
- if func_name == "gmail_api_request":
133
- if "messages" in endpoint:
134
- return "gmail", f"{method} Gmail messages"
135
- elif "drafts" in endpoint:
136
- return "gmail", f"{method} Gmail draft"
137
- elif "labels" in endpoint:
138
- return "gmail", f"{method} Gmail label"
139
- return "gmail", f"{method} Gmail API"
140
-
141
- elif func_name == "calendar_api_request":
142
- if "events" in endpoint:
143
- return "calendar", f"{method} Calendar events"
144
- return "calendar", f"{method} Google Calendar API"
145
-
146
- elif func_name == "drive_api_request":
147
- if "files" in endpoint:
148
- return "drive", f"{method} Drive files"
149
- return "drive", f"{method} Google Drive API"
150
-
151
- elif func_name == "notion_api_request":
152
- if "search" in endpoint:
153
- return "notion", "Search Notion workspace"
154
- elif "pages" in endpoint:
155
- return "notion", f"{method} Notion page"
156
- elif "databases" in endpoint:
157
- return "notion", f"{method} Notion database"
158
- elif "blocks" in endpoint:
159
- return "notion", f"{method} Notion blocks"
160
- return "notion", f"{method} Notion API"
161
-
162
- elif func_name == "github_api_request":
163
- if "issues" in endpoint:
164
- return "github", f"{method} GitHub issue"
165
- elif "pulls" in endpoint:
166
- return "github", f"{method} Pull Request"
167
- elif "repos" in endpoint:
168
- return "github", f"{method} Repository"
169
- elif "search" in endpoint:
170
- return "github", "Search GitHub"
171
- return "github", f"{method} GitHub API"
172
-
173
- return "search", func_name
174
-
175
- async def stream_chat_pipeline(request_obj, body: dict, http_client):
176
- user_prompt = body.get("prompt", "")
177
- req_messages = body.get("messages")
178
- images = body.get("images", [])
179
- user_id = body.get("user_id", "default_user")
180
- connectors = body.get("connectors", [])
181
- is_web_search = bool(body.get("web_search", False))
182
-
183
- messages = build_context_messages(req_messages, user_prompt, images)
184
- available_tools = get_filtered_tools(connectors, web_search=is_web_search)
185
-
186
- headers = {
187
- "Authorization": f"Bearer {BAI_API_KEY}",
188
- "Content-Type": "application/json",
189
- "Accept": "text/event-stream"
190
- }
191
-
192
- curr_messages = list(messages)
193
- max_turns = 5
194
- tool_step_counter = 0
195
- search_calls_count = 0
196
- executed_search_queries = {}
197
- aggregated_sources = []
198
-
199
- try:
200
- for turn_index in range(max_turns):
201
- active_tools = list(available_tools)
202
- if search_calls_count >= 3:
203
- active_tools = [t for t in active_tools if t.get("function", {}).get("name") != "tavily_search"]
204
-
205
- payload = {
206
- "model": MODEL_NAME,
207
- "messages": curr_messages,
208
- "temperature": 0.5,
209
- "top_p": 0.95,
210
- "max_tokens": 16384,
211
- "stream": True
212
- }
213
-
214
- if active_tools:
215
- payload["tools"] = active_tools
216
- payload["tool_choice"] = "auto"
217
-
218
- tool_calls_acc = []
219
- has_tool_call = False
220
-
221
- async with http_client.stream("POST", BAI_URL, headers=headers, json=payload) as response:
222
- async for line in response.aiter_lines():
223
- if await request_obj.is_disconnected():
224
- return
225
- if not line:
226
- continue
227
- line_str = line.strip()
228
- if line_str.startswith("data: "):
229
- data_content = line_str[6:].strip()
230
- if data_content == "[DONE]":
231
- break
232
- try:
233
- json_data = json.loads(data_content)
234
- choices = json_data.get("choices", [])
235
- if choices:
236
- delta = choices[0].get("delta", {})
237
- reasoning = delta.get("reasoning_content") or delta.get("reasoning")
238
- content = delta.get("content")
239
- tc_delta = delta.get("tool_calls")
240
-
241
- if reasoning:
242
- yield json.dumps({"thinking": reasoning}) + "\n"
243
-
244
- if tc_delta:
245
- has_tool_call = True
246
- for tc in tc_delta:
247
- idx = tc.get("index", 0)
248
- while len(tool_calls_acc) <= idx:
249
- tool_calls_acc.append({"id": "", "type": "function", "function": {"name": "", "arguments": ""}})
250
- if tc.get("id"):
251
- tool_calls_acc[idx]["id"] += tc["id"]
252
- if tc.get("function", {}).get("name"):
253
- tool_calls_acc[idx]["function"]["name"] += tc["function"]["name"]
254
- if tc.get("function", {}).get("arguments"):
255
- tool_calls_acc[idx]["function"]["arguments"] += tc["function"]["arguments"]
256
-
257
- if content and not has_tool_call:
258
- yield json.dumps({"answer": content}) + "\n"
259
- except json.JSONDecodeError:
260
- continue
261
-
262
- valid_tool_calls = [tc for tc in tool_calls_acc if tc.get("function", {}).get("name")]
263
- if has_tool_call and valid_tool_calls:
264
- for i, tc in enumerate(valid_tool_calls):
265
- if not tc.get("id"):
266
- tc["id"] = f"call_{turn_index}_{i+1}"
267
-
268
- curr_messages.append({
269
- "role": "assistant",
270
- "tool_calls": valid_tool_calls
271
- })
272
-
273
- for tc in valid_tool_calls:
274
- fn_name = tc.get("function", {}).get("name", "")
275
- args_raw = tc.get("function", {}).get("arguments", "{}")
276
- try:
277
- args_dict = json.loads(args_raw) if args_raw else {}
278
- except Exception:
279
- args_dict = {}
280
-
281
- tool_step_counter += 1
282
- icon_type, friendly_title = get_tool_metadata(fn_name, args_dict)
283
-
284
- step_add_payload = {
285
- "tool_event": "step_add",
286
- "step_index": tool_step_counter,
287
- "type": "tool_call",
288
- "name": fn_name,
289
- "title": friendly_title,
290
- "icon": icon_type,
291
- "request": args_dict,
292
- "response": ""
293
- }
294
- yield json.dumps(step_add_payload) + "\n"
295
-
296
- if fn_name == "tavily_search":
297
- q = args_dict.get("query", user_prompt or "").strip()
298
- depth = args_dict.get("search_depth", "basic")
299
- max_r = args_dict.get("max_results", 8)
300
-
301
- norm_q = normalize_query_for_cache(q)
302
-
303
- if norm_q in executed_search_queries:
304
- cached_res, cached_sources = executed_search_queries[norm_q]
305
- tool_res = cached_res
306
- sources_list = cached_sources
307
- else:
308
- search_calls_count += 1
309
- tool_res, sources_list = await execute_tavily_search(
310
- query=q,
311
- max_results=max_r,
312
- search_depth=depth
313
- )
314
- executed_search_queries[norm_q] = (tool_res, sources_list)
315
-
316
- if sources_list:
317
- for s in sources_list:
318
- if not any(existing.get("url") == s.get("url") for existing in aggregated_sources):
319
- aggregated_sources.append(s)
320
- yield json.dumps({"sources": aggregated_sources}) + "\n"
321
- else:
322
- tool_res = await execute_tool_call(user_id, fn_name, args_dict)
323
-
324
- step_update_payload = {
325
- "tool_event": "step_result",
326
- "step_index": tool_step_counter,
327
- "response": str(tool_res)
328
- }
329
- yield json.dumps(step_update_payload) + "\n"
330
-
331
- curr_messages.append({
332
- "role": "tool",
333
- "tool_call_id": tc.get("id"),
334
- "content": str(tool_res)
335
- })
336
- else:
337
- break
338
-
339
- if tool_step_counter > 0:
340
- yield json.dumps({"tool_event": "finished", "total_steps": tool_step_counter}) + "\n"
341
-
342
- except asyncio.CancelledError:
343
- pass