aient 1.0.37__py3-none-any.whl → 1.0.39__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.
aient/core/request.py CHANGED
@@ -675,6 +675,11 @@ async def get_gpt_payload(request, engine, provider, api_key=None):
675
675
  if "temperature" in payload:
676
676
  payload.pop("temperature")
677
677
 
678
+ # 代码生成/数学解题  0.0
679
+ # 数据抽取/分析 1.0
680
+ # 通用对话 1.3
681
+ # 翻译 1.3
682
+ # 创意类写作/诗歌创作 1.5
678
683
  if "deepseek-r" in original_model.lower():
679
684
  if "temperature" not in payload:
680
685
  payload["temperature"] = 0.6
aient/models/chatgpt.py CHANGED
@@ -12,7 +12,7 @@ from pathlib import Path
12
12
 
13
13
  from .base import BaseLLM
14
14
  from ..plugins import PLUGINS, get_tools_result_async, function_call_list, update_tools_config
15
- from ..utils.scripts import check_json, safe_get, async_generator_to_sync
15
+ from ..utils.scripts import check_json, safe_get, async_generator_to_sync, parse_function_xml
16
16
  from ..core.request import prepare_request_payload
17
17
  from ..core.response import fetch_response_stream
18
18
 
@@ -131,20 +131,26 @@ class chatgpt(BaseLLM):
131
131
  matching_message = next(filter(lambda x: safe_get(x, "tool_calls", 0, "function", "name", default="") == 'get_next_pdf', self.conversation[convo_id]), None)
132
132
  if matching_message is not None:
133
133
  self.conversation[convo_id] = self.conversation[convo_id][:self.conversation[convo_id].index(matching_message)]
134
- self.conversation[convo_id].append({
135
- "role": "assistant",
136
- "tool_calls": [
137
- {
138
- "id": function_call_id,
139
- "type": "function",
140
- "function": {
141
- "name": function_name,
142
- "arguments": function_arguments,
143
- },
144
- }
145
- ],
146
- })
147
- self.conversation[convo_id].append({"role": role, "tool_call_id": function_call_id, "content": message})
134
+
135
+ if not (all(value == False for value in self.plugins.values()) or self.use_plugins == False):
136
+ self.conversation[convo_id].append({
137
+ "role": "assistant",
138
+ "tool_calls": [
139
+ {
140
+ "id": function_call_id,
141
+ "type": "function",
142
+ "function": {
143
+ "name": function_name,
144
+ "arguments": function_arguments,
145
+ },
146
+ }
147
+ ],
148
+ })
149
+ self.conversation[convo_id].append({"role": role, "tool_call_id": function_call_id, "content": message})
150
+ else:
151
+ self.conversation[convo_id].append({"role": "assistant", "content": "I will use tool: " + function_name + ". tool arguments:" + function_arguments + ". I will get the tool call result in the next user response."})
152
+ self.conversation[convo_id].append({"role": "user", "content": f"[{function_name} Result]\n\n" + message})
153
+
148
154
  else:
149
155
  print('\033[31m')
150
156
  print("error: add_to_conversation message is None or empty")
@@ -390,6 +396,13 @@ class chatgpt(BaseLLM):
390
396
  if response_role is None:
391
397
  response_role = "assistant"
392
398
 
399
+ function_parameter = parse_function_xml(full_response)
400
+ if function_parameter['function_name']:
401
+ need_function_call = True
402
+ function_call_name = function_parameter['function_name']
403
+ function_full_response = json.dumps(function_parameter['parameter'])
404
+ function_call_id = function_parameter['function_name'] + "_tool_call"
405
+
393
406
  # 处理函数调用
394
407
  if need_function_call:
395
408
  if self.print_log:
aient/utils/scripts.py CHANGED
@@ -231,5 +231,321 @@ def async_generator_to_sync(async_gen):
231
231
  except Exception as e:
232
232
  print(f"Error during cleanup: {e}")
233
233
 
234
+ def parse_tools_from_cursor_prompt(text):
235
+ import json
236
+ import re
237
+
238
+ # 从 cursor_prompt 中提取 <tools> 标签内的 JSON 字符串
239
+ tools_match = re.search(r"<tools>\n(.*?)\n</tools>", text, re.DOTALL)
240
+ if tools_match:
241
+ tools_json_string = tools_match.group(1).strip()
242
+ try:
243
+ tools_list_data = json.loads(tools_json_string, strict=False)
244
+ return tools_list_data
245
+ except json.JSONDecodeError as e:
246
+ print(f"解析 JSON 时出错: {e}")
247
+ return []
248
+
249
+ from dataclasses import dataclass
250
+ from typing import List, Callable, Optional, TypeVar, Generic, Union, Dict, Any
251
+
252
+ # 定义结果类型
253
+ @dataclass
254
+ class XmlMatcherResult:
255
+ matched: bool
256
+ data: str = ""
257
+
258
+ # 泛型类型变量,用于 transform 的返回类型
259
+ R = TypeVar('R')
260
+
261
+ class XmlMatcher(Generic[R]):
262
+ def __init__(self,
263
+ tag_name: str,
264
+ transform: Optional[Callable[[XmlMatcherResult], R]] = None,
265
+ position: int = 0):
266
+ self.tag_name: str = tag_name
267
+ self.transform: Optional[Callable[[XmlMatcherResult], R]] = transform
268
+ self.position: int = position
269
+
270
+ self.index: int = 0
271
+ self.chunks: List[XmlMatcherResult] = []
272
+ self.cached: List[str] = []
273
+ self.matched: bool = False
274
+ self.state: str = "TEXT" # "TEXT", "TAG_OPEN", "TAG_CLOSE"
275
+ self.depth: int = 0
276
+ self.pointer: int = 0
277
+
278
+ def _collect(self):
279
+ """将缓存的字符收集到 chunks 中"""
280
+ if not self.cached:
281
+ return
282
+
283
+ data = "".join(self.cached)
284
+ # 检查是否需要合并到上一个 chunk
285
+ # 仅当当前缓存的匹配状态与上一个 chunk 相同时才合并
286
+ last = self.chunks[-1] if self.chunks else None
287
+ current_matched_state = self.matched if self.state == "TEXT" else (self.depth > 0) # 在标签解析过程中,匹配状态取决于深度
288
+
289
+ if last and last.matched == current_matched_state:
290
+ last.data += data
291
+ else:
292
+ # 只有当 data 不为空时才添加新的 chunk
293
+ if data:
294
+ self.chunks.append(XmlMatcherResult(data=data, matched=current_matched_state))
295
+
296
+ self.cached = []
297
+
298
+ def _pop(self) -> List[Union[XmlMatcherResult, R]]:
299
+ """返回处理过的 chunks 并清空列表"""
300
+ chunks_to_return = self.chunks
301
+ self.chunks = []
302
+ if not self.transform:
303
+ # 如果没有 transform 函数,直接返回原始结果列表
304
+ # 需要显式类型转换,因为泛型 R 默认为 XmlMatcherResult
305
+ return [chunk for chunk in chunks_to_return] # type: ignore
306
+ # 应用 transform 函数
307
+ return [self.transform(chunk) for chunk in chunks_to_return]
308
+
309
+ def _update(self, chunk: str):
310
+ """处理输入字符串块的核心逻辑"""
311
+ for char in chunk:
312
+ current_char_processed = False # 标记当前字符是否已被状态机逻辑处理
313
+
314
+ if self.state == "TEXT":
315
+ if char == "<" and (self.pointer >= self.position or self.matched):
316
+ self._collect()
317
+ self.state = "TAG_OPEN"
318
+ self.cached.append(char)
319
+ self.index = 0 # 重置 index 以开始匹配标签名或跳过空格
320
+ current_char_processed = True
321
+ # else: 保持在 TEXT 状态,字符将在循环末尾添加到 cached
322
+
323
+ elif self.state == "TAG_OPEN":
324
+ self.cached.append(char)
325
+ current_char_processed = True
326
+
327
+ tag_name_len = len(self.tag_name)
328
+
329
+ # 状态: 刚进入 < 之后
330
+ if self.index == 0:
331
+ if char == "/":
332
+ self.state = "TAG_CLOSE"
333
+ # index 保持 0,准备匹配闭合标签名或跳过空格
334
+ elif char.isspace():
335
+ # 跳过 < 后的空格
336
+ pass # index 保持 0
337
+ elif char == self.tag_name[0]:
338
+ # 开始匹配标签名
339
+ self.index = 1
340
+ else:
341
+ # 无效标签开头 (不是 /,不是空格,不是 tag_name[0])
342
+ self.state = "TEXT"
343
+ current_char_processed = True
344
+ # 状态: 正在匹配标签名
345
+ elif self.index < tag_name_len:
346
+ if self.tag_name[self.index] == char:
347
+ self.index += 1
348
+ # 允许在标签名匹配过程中遇到空格,视为属性或无效字符处理
349
+ elif char.isspace():
350
+ # 遇到空格,表示标签名已结束,进入属性/结束符处理
351
+ # 将 index 设置为 tag_name_len 以便后续逻辑处理
352
+ # 但前提是当前 index 确实匹配到了 tag_name
353
+ # 如果是 <t hink> 这种情况,这里会失败
354
+ # 为了简化,我们不允许标签名内部有空格,如果需要,逻辑会更复杂
355
+ # 因此,如果这里遇到空格但 index < tag_name_len,视为无效
356
+ self.state = "TEXT"
357
+ current_char_processed = True
358
+ else:
359
+ # 字符不匹配标签名
360
+ self.state = "TEXT"
361
+ current_char_processed = True
362
+ # 状态: 标签名已完全匹配 (self.index == tag_name_len)
363
+ else: # self.index >= tag_name_len (实际是 ==)
364
+ if char == ">":
365
+ # 找到了开始标签的结束符
366
+ self.state = "TEXT"
367
+ self.depth += 1
368
+ self.matched = True
369
+ self.cached = [] # 清空缓存,丢弃 <tag ...>
370
+ elif char.isspace():
371
+ # 忽略标签名后的空格
372
+ pass # 保持在 TAG_OPEN 状态,等待 > 或属性
373
+ else:
374
+ # 字符是属性的一部分,忽略它,继续等待 '>'
375
+ pass # 保持在 TAG_OPEN 状态
376
+
377
+ elif self.state == "TAG_CLOSE":
378
+ self.cached.append(char)
379
+ current_char_processed = True # 默认设为 True
380
+
381
+ tag_name_len = len(self.tag_name)
382
+
383
+ # 状态: 刚进入 </ 之后
384
+ if self.index == 0:
385
+ if char.isspace():
386
+ # 跳过 </ 后的空格
387
+ pass # index 保持 0
388
+ elif char == self.tag_name[0]:
389
+ # 开始匹配标签名
390
+ self.index = 1
391
+ else:
392
+ # 无效闭合标签 (不是空格,不是 tag_name[0])
393
+ self.state = "TEXT"
394
+ current_char_processed = True
395
+ # 状态: 正在匹配标签名
396
+ elif self.index < tag_name_len:
397
+ if self.tag_name[self.index] == char:
398
+ self.index += 1
399
+ else:
400
+ # 字符不匹配标签名
401
+ self.state = "TEXT"
402
+ current_char_processed = True
403
+ # 状态: 标签名已完全匹配 (self.index == tag_name_len)
404
+ else: # self.index == tag_name_len
405
+ if char == ">":
406
+ # 找到了 '>'
407
+ was_inside_tag = self.depth > 0
408
+ self.state = "TEXT" # 无论如何都回到 TEXT 状态
409
+
410
+ if was_inside_tag:
411
+ # 确实在标签内部,正常处理闭合标签
412
+ self.depth -= 1
413
+ self.matched = self.depth > 0
414
+ self.cached = [] # 清空缓存,丢弃 </tag>
415
+ # current_char_processed 保持 True
416
+ else:
417
+ # 不在标签内部,这是一个无效/意外的闭合标签
418
+ # 将其视为普通文本,但阻止最后的 > 被添加到缓存
419
+ # 保留 cached 中已有的 '</tag' 部分,它们将在下次 collect 时作为文本处理
420
+ current_char_processed = True # 标记 '>' 已处理,防止循环末尾再次添加
421
+
422
+ elif char.isspace():
423
+ # 允许 </tag >, 继续等待 '>'
424
+ pass # 保持在 TAG_CLOSE 状态, current_char_processed 保持 True
425
+ else:
426
+ # 闭合标签名后出现非空格、非 > 的字符
427
+ self.state = "TEXT"
428
+ current_char_processed = True
429
+
430
+ # 如果当前字符未被状态机逻辑处理(即应视为普通文本)
431
+ if not current_char_processed:
432
+ # 确保状态是 TEXT
433
+ if self.state != "TEXT":
434
+ # 如果之前在尝试匹配标签但失败了,缓存的内容应视为文本
435
+ self.state = "TEXT"
436
+
437
+ self.cached.append(char)
438
+
439
+ self.pointer += 1
440
+
441
+ # 在处理完整个 chunk 后,如果状态是 TEXT,收集剩余缓存
442
+ if self.state == "TEXT":
443
+ self._collect()
444
+
445
+
446
+ def final(self, chunk: Optional[str] = None) -> List[Union[XmlMatcherResult, R]]:
447
+ """处理最后一块数据并返回所有结果"""
448
+ if chunk:
449
+ self._update(chunk)
450
+ # 确保所有剩余缓存都被收集
451
+ # 即使状态不是 TEXT,也需要收集,以防有未闭合的标签等情况
452
+ self._collect()
453
+ return self._pop()
454
+
455
+ def update(self, chunk: str) -> List[Union[XmlMatcherResult, R]]:
456
+ """处理一块数据并返回当前处理的结果"""
457
+ self._update(chunk)
458
+ return self._pop()
459
+
460
+ def parse_function_xml(xml_content: str) -> Dict[str, Any]:
461
+ """
462
+ 解析XML格式的函数调用信息,转换为字典格式
463
+
464
+ 参数:
465
+ xml_content: 包含函数调用的XML字符串
466
+
467
+ 返回:
468
+ 包含函数名和参数的字典
469
+ """
470
+ # 首先,找出根标签(函数名)
471
+ # function_matcher = XmlMatcher[XmlMatcherResult]("", position=0)
472
+ # results = function_matcher.final(xml_content)
473
+
474
+ # 找到第一个匹配的标签
475
+ function_name = ""
476
+ function_content = ""
477
+
478
+ # 寻找第一个开始标签
479
+ tag_start = xml_content.find("<")
480
+ if tag_start != -1:
481
+ tag_end = xml_content.find(">", tag_start)
482
+ if tag_end != -1:
483
+ # 提取标签名(函数名)
484
+ tag_content = xml_content[tag_start+1:tag_end].strip()
485
+ # 处理可能有属性的情况
486
+ function_name = tag_content.split()[0] if " " in tag_content else tag_content
487
+
488
+ # 使用XmlMatcher提取该函数标签内的内容
489
+ content_matcher = XmlMatcher[XmlMatcherResult](function_name)
490
+ match_results = content_matcher.final(xml_content)
491
+
492
+ for result in match_results:
493
+ if result.matched:
494
+ function_content = result.data
495
+ break
496
+
497
+ # 如果没有找到函数名或内容,返回空结果
498
+ if not function_name or not function_content:
499
+ return {'function_name': '', 'parameter': {}}
500
+
501
+ # 解析参数
502
+ parameters = {}
503
+ lines = function_content.strip().split('\n')
504
+ current_param = None
505
+ current_value = []
506
+
507
+ for line in lines:
508
+ line = line.strip()
509
+ if line.startswith('<') and '>' in line:
510
+ # 新参数开始
511
+ if current_param and current_value:
512
+ # 保存之前的参数
513
+ parameters[current_param] = '\n'.join(current_value).strip()
514
+ current_value = []
515
+
516
+ # 提取参数名
517
+ param_start = line.find('<') + 1
518
+ param_end = line.find('>', param_start)
519
+ if param_end != -1:
520
+ param = line[param_start:param_end]
521
+ # 检查是否是闭合标签
522
+ if param.startswith('/'):
523
+ if param[1:] == current_param:
524
+ current_param = None
525
+ else:
526
+ current_param = param
527
+ # 检查是否在同一行有值
528
+ rest = line[param_end+1:]
529
+ if rest and not rest.startswith('</'):
530
+ current_value.append(rest)
531
+ elif current_param:
532
+ # 继续收集当前参数的值
533
+ current_value.append(line)
534
+
535
+ # 处理最后一个参数
536
+ if current_param and current_value:
537
+ parameters[current_param] = '\n'.join(current_value).strip()
538
+
539
+ # 清理参数值中可能的结束标签
540
+ for param, value in parameters.items():
541
+ end_tag = f'</{param}>'
542
+ if value.endswith(end_tag):
543
+ parameters[param] = value[:-len(end_tag)].strip()
544
+
545
+ return {
546
+ 'function_name': function_name,
547
+ 'parameter': parameters
548
+ }
549
+
234
550
  if __name__ == "__main__":
235
551
  os.system("clear")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.0.37
3
+ Version: 1.0.39
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -3,7 +3,7 @@ aient/core/.git,sha256=lrAcW1SxzRBUcUiuKL5tS9ykDmmTXxyLP3YYU-Y-Q-I,45
3
3
  aient/core/__init__.py,sha256=NxjebTlku35S4Dzr16rdSqSTWUvvwEeACe8KvHJnjPg,34
4
4
  aient/core/log_config.py,sha256=kz2_yJv1p-o3lUQOwA3qh-LSc3wMHv13iCQclw44W9c,274
5
5
  aient/core/models.py,sha256=8MsuiYHBHVR5UMQ_cNLkvntoxalS7NpVwaNwHA0iZmk,7379
6
- aient/core/request.py,sha256=Tk8ylLBHPsrA4C_fb2XUEz_ZM7tR4691mlIxn7x8LUU,48249
6
+ aient/core/request.py,sha256=-nyFwGM86LB8Zn6ScRJvAbkJ9LPCHjgg51tO_edAIZ4,48422
7
7
  aient/core/response.py,sha256=7s1Jil0E5nnbL9xQldcjHIqSp0MFeWQo9mNX_iAuvSk,25954
8
8
  aient/core/utils.py,sha256=i9ZwyywBLIhRM0fNmFSD3jF3dBL5QqVMOtSlG_ddv-I,24101
9
9
  aient/core/test/test_base_api.py,sha256=CjfFzMG26r8C4xCPoVkKb3Ac6pp9gy5NUCbZJHoSSsM,393
@@ -12,7 +12,7 @@ aient/core/test/test_payload.py,sha256=8jBiJY1uidm1jzL-EiK0s6UGmW9XkdsuuKFGrwFhF
12
12
  aient/models/__init__.py,sha256=ouNDNvoBBpIFrLsk09Q_sq23HR0GbLAKfGLIFmfEuXE,219
13
13
  aient/models/audio.py,sha256=kRd-8-WXzv4vwvsTGwnstK-WR8--vr9CdfCZzu8y9LA,1934
14
14
  aient/models/base.py,sha256=Loyt2F2WrDMBbK-sdmTtgkLVtdUXxK5tg4qoI6nc0Xo,7527
15
- aient/models/chatgpt.py,sha256=nyYGg-MaQwavwhqrb_J8Px-bVkOCubL0oWx-elMwqW0,37667
15
+ aient/models/chatgpt.py,sha256=UcadvLiBh4a3VHEMRxb4hZWXqZ899QP8Ii81BRegmQ4,38604
16
16
  aient/models/claude.py,sha256=thK9P8qkaaoUN3OOJ9Shw4KDs-pAGKPoX4FOPGFXva8,28597
17
17
  aient/models/duckduckgo.py,sha256=1l7vYCs9SG5SWPCbcl7q6pCcB5AUF_r-a4l9frz3Ogo,8115
18
18
  aient/models/gemini.py,sha256=chGLc-8G_DAOxr10HPoOhvVFW1RvMgHd6mt--VyAW98,14730
@@ -28,9 +28,9 @@ aient/plugins/today.py,sha256=btnXJNqWorJDKPvH9PBTdHaExpVI1YPuSAeRrq-fg9A,667
28
28
  aient/plugins/websearch.py,sha256=yiBzqXK5X220ibR-zko3VDsn4QOnLu1k6E2YOygCeTQ,15185
29
29
  aient/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  aient/utils/prompt.py,sha256=UcSzKkFE4-h_1b6NofI6xgk3GoleqALRKY8VBaXLjmI,11311
31
- aient/utils/scripts.py,sha256=O-0IXN3mezPauFs6fw83WDDgklpXTDvcbJBNTDrsIG0,8201
32
- aient-1.0.37.dist-info/licenses/LICENSE,sha256=XNdbcWldt0yaNXXWB_Bakoqnxb3OVhUft4MgMA_71ds,1051
33
- aient-1.0.37.dist-info/METADATA,sha256=pWUJwtfmBUAZODvCqezKuQHL35DsSdvGTa8xCu6m37I,4986
34
- aient-1.0.37.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
- aient-1.0.37.dist-info/top_level.txt,sha256=3oXzrP5sAVvyyqabpeq8A2_vfMtY554r4bVE-OHBrZk,6
36
- aient-1.0.37.dist-info/RECORD,,
31
+ aient/utils/scripts.py,sha256=7QCR7iQcWtwLgrhRFxQzPeb20N0kB6-T9MxDoSpTsjk,21573
32
+ aient-1.0.39.dist-info/licenses/LICENSE,sha256=XNdbcWldt0yaNXXWB_Bakoqnxb3OVhUft4MgMA_71ds,1051
33
+ aient-1.0.39.dist-info/METADATA,sha256=PPeUAD0T4V8KtmWiiVZ5dPjScpY9HbWA4gAdkKkACk0,4986
34
+ aient-1.0.39.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
+ aient-1.0.39.dist-info/top_level.txt,sha256=3oXzrP5sAVvyyqabpeq8A2_vfMtY554r4bVE-OHBrZk,6
36
+ aient-1.0.39.dist-info/RECORD,,
File without changes