AstrBot 4.10.2__py3-none-any.whl → 4.10.4__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. astrbot/builtin_stars/astrbot/long_term_memory.py +186 -0
  2. astrbot/builtin_stars/astrbot/main.py +120 -0
  3. astrbot/builtin_stars/astrbot/metadata.yaml +4 -0
  4. astrbot/builtin_stars/astrbot/process_llm_request.py +245 -0
  5. astrbot/builtin_stars/builtin_commands/commands/__init__.py +31 -0
  6. astrbot/builtin_stars/builtin_commands/commands/admin.py +77 -0
  7. astrbot/builtin_stars/builtin_commands/commands/alter_cmd.py +173 -0
  8. astrbot/builtin_stars/builtin_commands/commands/conversation.py +366 -0
  9. astrbot/builtin_stars/builtin_commands/commands/help.py +88 -0
  10. astrbot/builtin_stars/builtin_commands/commands/llm.py +20 -0
  11. astrbot/builtin_stars/builtin_commands/commands/persona.py +142 -0
  12. astrbot/builtin_stars/builtin_commands/commands/plugin.py +120 -0
  13. astrbot/builtin_stars/builtin_commands/commands/provider.py +329 -0
  14. astrbot/builtin_stars/builtin_commands/commands/setunset.py +36 -0
  15. astrbot/builtin_stars/builtin_commands/commands/sid.py +36 -0
  16. astrbot/builtin_stars/builtin_commands/commands/t2i.py +23 -0
  17. astrbot/builtin_stars/builtin_commands/commands/tool.py +31 -0
  18. astrbot/builtin_stars/builtin_commands/commands/tts.py +36 -0
  19. astrbot/builtin_stars/builtin_commands/commands/utils/rst_scene.py +26 -0
  20. astrbot/builtin_stars/builtin_commands/main.py +237 -0
  21. astrbot/builtin_stars/builtin_commands/metadata.yaml +4 -0
  22. astrbot/builtin_stars/python_interpreter/main.py +536 -0
  23. astrbot/builtin_stars/python_interpreter/metadata.yaml +4 -0
  24. astrbot/builtin_stars/python_interpreter/requirements.txt +1 -0
  25. astrbot/builtin_stars/python_interpreter/shared/api.py +22 -0
  26. astrbot/builtin_stars/reminder/main.py +266 -0
  27. astrbot/builtin_stars/reminder/metadata.yaml +4 -0
  28. astrbot/builtin_stars/session_controller/main.py +114 -0
  29. astrbot/builtin_stars/session_controller/metadata.yaml +5 -0
  30. astrbot/builtin_stars/web_searcher/engines/__init__.py +111 -0
  31. astrbot/builtin_stars/web_searcher/engines/bing.py +30 -0
  32. astrbot/builtin_stars/web_searcher/engines/sogo.py +52 -0
  33. astrbot/builtin_stars/web_searcher/main.py +436 -0
  34. astrbot/builtin_stars/web_searcher/metadata.yaml +4 -0
  35. astrbot/cli/__init__.py +1 -1
  36. astrbot/core/agent/message.py +32 -1
  37. astrbot/core/agent/runners/tool_loop_agent_runner.py +26 -8
  38. astrbot/core/astr_agent_hooks.py +6 -0
  39. astrbot/core/backup/__init__.py +26 -0
  40. astrbot/core/backup/constants.py +77 -0
  41. astrbot/core/backup/exporter.py +477 -0
  42. astrbot/core/backup/importer.py +761 -0
  43. astrbot/core/config/astrbot_config.py +2 -0
  44. astrbot/core/config/default.py +47 -6
  45. astrbot/core/knowledge_base/chunking/recursive.py +10 -2
  46. astrbot/core/log.py +1 -1
  47. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +184 -174
  48. astrbot/core/pipeline/result_decorate/stage.py +65 -57
  49. astrbot/core/pipeline/waking_check/stage.py +31 -3
  50. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +15 -29
  51. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +1 -6
  52. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +15 -1
  53. astrbot/core/platform/sources/lark/lark_adapter.py +2 -10
  54. astrbot/core/platform/sources/misskey/misskey_adapter.py +0 -5
  55. astrbot/core/platform/sources/misskey/misskey_utils.py +0 -3
  56. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +4 -9
  57. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +4 -9
  58. astrbot/core/platform/sources/satori/satori_adapter.py +6 -1
  59. astrbot/core/platform/sources/slack/slack_adapter.py +3 -6
  60. astrbot/core/platform/sources/webchat/webchat_adapter.py +0 -1
  61. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +3 -5
  62. astrbot/core/provider/entities.py +41 -10
  63. astrbot/core/provider/provider.py +3 -1
  64. astrbot/core/provider/sources/anthropic_source.py +140 -30
  65. astrbot/core/provider/sources/fishaudio_tts_api_source.py +14 -6
  66. astrbot/core/provider/sources/gemini_source.py +112 -29
  67. astrbot/core/provider/sources/minimax_tts_api_source.py +4 -1
  68. astrbot/core/provider/sources/openai_source.py +93 -56
  69. astrbot/core/provider/sources/xai_source.py +29 -0
  70. astrbot/core/provider/sources/xinference_stt_provider.py +24 -12
  71. astrbot/core/star/context.py +1 -1
  72. astrbot/core/star/star_manager.py +52 -13
  73. astrbot/core/utils/astrbot_path.py +34 -0
  74. astrbot/core/utils/pip_installer.py +20 -1
  75. astrbot/dashboard/routes/__init__.py +2 -0
  76. astrbot/dashboard/routes/backup.py +1093 -0
  77. astrbot/dashboard/routes/config.py +45 -0
  78. astrbot/dashboard/routes/log.py +44 -10
  79. astrbot/dashboard/server.py +9 -1
  80. {astrbot-4.10.2.dist-info → astrbot-4.10.4.dist-info}/METADATA +1 -1
  81. {astrbot-4.10.2.dist-info → astrbot-4.10.4.dist-info}/RECORD +84 -44
  82. {astrbot-4.10.2.dist-info → astrbot-4.10.4.dist-info}/WHEEL +0 -0
  83. {astrbot-4.10.2.dist-info → astrbot-4.10.4.dist-info}/entry_points.txt +0 -0
  84. {astrbot-4.10.2.dist-info → astrbot-4.10.4.dist-info}/licenses/LICENSE +0 -0
@@ -80,6 +80,8 @@ class AstrBotConfig(dict):
80
80
  if v["type"] == "object":
81
81
  conf[k] = {}
82
82
  _parse_schema(v["items"], conf[k])
83
+ elif v["type"] == "template_list":
84
+ conf[k] = default
83
85
  else:
84
86
  conf[k] = default
85
87
 
@@ -5,7 +5,7 @@ from typing import Any, TypedDict
5
5
 
6
6
  from astrbot.core.utils.astrbot_path import get_astrbot_data_path
7
7
 
8
- VERSION = "4.10.2"
8
+ VERSION = "4.10.4"
9
9
  DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
10
10
 
11
11
  WEBHOOK_SUPPORTED_PLATFORMS = [
@@ -905,6 +905,7 @@ CONFIG_METADATA_2 = {
905
905
  "key": [],
906
906
  "api_base": "https://api.anthropic.com/v1",
907
907
  "timeout": 120,
908
+ "anth_thinking_config": {"budget": 0},
908
909
  },
909
910
  "Moonshot": {
910
911
  "id": "moonshot",
@@ -920,7 +921,7 @@ CONFIG_METADATA_2 = {
920
921
  "xAI": {
921
922
  "id": "xai",
922
923
  "provider": "xai",
923
- "type": "openai_chat_completion",
924
+ "type": "xai_chat_completion",
924
925
  "provider_type": "chat_completion",
925
926
  "enable": True,
926
927
  "key": [],
@@ -1286,7 +1287,7 @@ CONFIG_METADATA_2 = {
1286
1287
  "minimax-is-timber-weight": False,
1287
1288
  "minimax-voice-id": "female-shaonv",
1288
1289
  "minimax-timber-weight": '[\n {\n "voice_id": "Chinese (Mandarin)_Warm_Girl",\n "weight": 25\n },\n {\n "voice_id": "Chinese (Mandarin)_BashfulGirl",\n "weight": 50\n }\n]',
1289
- "minimax-voice-emotion": "neutral",
1290
+ "minimax-voice-emotion": "auto",
1290
1291
  "minimax-voice-latex": False,
1291
1292
  "minimax-voice-english-normalization": False,
1292
1293
  "timeout": 20,
@@ -1450,7 +1451,32 @@ CONFIG_METADATA_2 = {
1450
1451
  "description": "自定义请求体参数",
1451
1452
  "type": "dict",
1452
1453
  "items": {},
1453
- "hint": "此处添加的键值对将被合并到发送给 API 的 extra_body 中。值可以是字符串、数字或布尔值。",
1454
+ "hint": "用于在请求时添加额外的参数,如 temperature、top_p、max_tokens 等。",
1455
+ "template_schema": {
1456
+ "temperature": {
1457
+ "name": "Temperature",
1458
+ "description": "温度参数",
1459
+ "hint": "控制输出的随机性,范围通常为 0-2。值越高越随机。",
1460
+ "type": "float",
1461
+ "default": 0.6,
1462
+ "slider": {"min": 0, "max": 2, "step": 0.1},
1463
+ },
1464
+ "top_p": {
1465
+ "name": "Top-p",
1466
+ "description": "Top-p 采样",
1467
+ "hint": "核采样参数,范围通常为 0-1。控制模型考虑的概率质量。",
1468
+ "type": "float",
1469
+ "default": 1.0,
1470
+ "slider": {"min": 0, "max": 1, "step": 0.01},
1471
+ },
1472
+ "max_tokens": {
1473
+ "name": "Max Tokens",
1474
+ "description": "最大令牌数",
1475
+ "hint": "生成的最大令牌数。",
1476
+ "type": "int",
1477
+ "default": 8192,
1478
+ },
1479
+ },
1454
1480
  },
1455
1481
  "provider": {
1456
1482
  "type": "string",
@@ -1787,6 +1813,17 @@ CONFIG_METADATA_2 = {
1787
1813
  },
1788
1814
  },
1789
1815
  },
1816
+ "anth_thinking_config": {
1817
+ "description": "Thinking Config",
1818
+ "type": "object",
1819
+ "items": {
1820
+ "budget": {
1821
+ "description": "Thinking Budget",
1822
+ "type": "int",
1823
+ "hint": "Anthropic thinking.budget_tokens param. Must >= 1024. See: https://platform.claude.com/docs/en/build-with-claude/extended-thinking",
1824
+ },
1825
+ },
1826
+ },
1790
1827
  "minimax-group-id": {
1791
1828
  "type": "string",
1792
1829
  "description": "用户组",
@@ -1858,15 +1895,18 @@ CONFIG_METADATA_2 = {
1858
1895
  "minimax-voice-emotion": {
1859
1896
  "type": "string",
1860
1897
  "description": "情绪",
1861
- "hint": "控制合成语音的情绪",
1898
+ "hint": "控制合成语音的情绪。当为 auto 时,将根据文本内容自动选择情绪。",
1862
1899
  "options": [
1900
+ "auto",
1863
1901
  "happy",
1864
1902
  "sad",
1865
1903
  "angry",
1866
1904
  "fearful",
1867
1905
  "disgusted",
1868
1906
  "surprised",
1869
- "neutral",
1907
+ "calm",
1908
+ "fluent",
1909
+ "whisper",
1870
1910
  ],
1871
1911
  },
1872
1912
  "minimax-voice-latex": {
@@ -3049,4 +3089,5 @@ DEFAULT_VALUE_MAP = {
3049
3089
  "text": "",
3050
3090
  "list": [],
3051
3091
  "object": {},
3092
+ "template_list": [],
3052
3093
  }
@@ -149,8 +149,16 @@ class RecursiveCharacterChunker(BaseChunker):
149
149
  分割后的文本块列表
150
150
 
151
151
  """
152
- chunk_size = chunk_size or self.chunk_size
153
- overlap = overlap or self.chunk_overlap
152
+ if chunk_size is None:
153
+ chunk_size = self.chunk_size
154
+ if overlap is None:
155
+ overlap = self.chunk_overlap
156
+ if chunk_size <= 0:
157
+ raise ValueError("chunk_size must be greater than 0")
158
+ if overlap < 0:
159
+ raise ValueError("chunk_overlap must be non-negative")
160
+ if overlap >= chunk_size:
161
+ raise ValueError("chunk_overlap must be less than chunk_size")
154
162
  result = []
155
163
  for i in range(0, len(text), chunk_size - overlap):
156
164
  end = min(i + chunk_size, len(text))
astrbot/core/log.py CHANGED
@@ -58,7 +58,7 @@ def is_plugin_path(pathname):
58
58
  return False
59
59
 
60
60
  norm_path = os.path.normpath(pathname)
61
- return ("data/plugins" in norm_path) or ("packages/" in norm_path)
61
+ return ("data/plugins" in norm_path) or ("astrbot/builtin_stars/" in norm_path)
62
62
 
63
63
 
64
64
  def get_short_level_name(level_name):
@@ -6,6 +6,7 @@ import json
6
6
  from collections.abc import AsyncGenerator
7
7
 
8
8
  from astrbot.core import logger
9
+ from astrbot.core.agent.message import Message
9
10
  from astrbot.core.agent.tool import ToolSet
10
11
  from astrbot.core.astr_agent_context import AstrAgentContext
11
12
  from astrbot.core.conversation_mgr import Conversation
@@ -294,6 +295,7 @@ class InternalAgentSubStage(Stage):
294
295
  event: AstrMessageEvent,
295
296
  req: ProviderRequest,
296
297
  llm_response: LLMResponse | None,
298
+ all_messages: list[Message],
297
299
  ):
298
300
  if (
299
301
  not req
@@ -307,31 +309,23 @@ class InternalAgentSubStage(Stage):
307
309
  logger.debug("LLM 响应为空,不保存记录。")
308
310
  return
309
311
 
310
- if req.contexts is None:
311
- req.contexts = []
312
-
313
- # 历史上下文
314
- messages = copy.deepcopy(req.contexts)
315
- # 这一轮对话请求的用户输入
316
- messages.append(await req.assemble_context())
317
- # 这一轮对话的 LLM 响应
318
- if req.tool_calls_result:
319
- if not isinstance(req.tool_calls_result, list):
320
- messages.extend(req.tool_calls_result.to_openai_messages())
321
- elif isinstance(req.tool_calls_result, list):
322
- for tcr in req.tool_calls_result:
323
- messages.extend(tcr.to_openai_messages())
324
- messages.append(
325
- {
326
- "role": "assistant",
327
- "content": llm_response.completion_text or "*No response*",
328
- }
329
- )
330
- messages = list(filter(lambda item: "_no_save" not in item, messages))
312
+ # using agent context messages to save to history
313
+ message_to_save = []
314
+ for message in all_messages:
315
+ if message.role == "system":
316
+ # we do not save system messages to history
317
+ continue
318
+ if message.role in ["assistant", "user"] and getattr(
319
+ message, "_no_save", None
320
+ ):
321
+ # we do not save user and assistant messages that are marked as _no_save
322
+ continue
323
+ message_to_save.append(message.model_dump())
324
+
331
325
  await self.conv_manager.update_conversation(
332
326
  event.unified_msg_origin,
333
327
  req.conversation.cid,
334
- history=messages,
328
+ history=message_to_save,
335
329
  )
336
330
 
337
331
  def _fix_messages(self, messages: list[dict]) -> list[dict]:
@@ -355,174 +349,190 @@ class InternalAgentSubStage(Stage):
355
349
  ) -> AsyncGenerator[None, None]:
356
350
  req: ProviderRequest | None = None
357
351
 
358
- provider = self._select_provider(event)
359
- if provider is None:
360
- return
361
- if not isinstance(provider, Provider):
362
- logger.error(f"选择的提供商类型无效({type(provider)}),跳过 LLM 请求处理。")
363
- return
364
-
365
- streaming_response = self.streaming_response
366
- if (enable_streaming := event.get_extra("enable_streaming")) is not None:
367
- streaming_response = bool(enable_streaming)
368
-
369
- logger.debug("ready to request llm provider")
370
- async with session_lock_manager.acquire_lock(event.unified_msg_origin):
371
- logger.debug("acquired session lock for llm request")
372
- if event.get_extra("provider_request"):
373
- req = event.get_extra("provider_request")
374
- assert isinstance(req, ProviderRequest), (
375
- "provider_request 必须是 ProviderRequest 类型。"
352
+ try:
353
+ provider = self._select_provider(event)
354
+ if provider is None:
355
+ return
356
+ if not isinstance(provider, Provider):
357
+ logger.error(
358
+ f"选择的提供商类型无效({type(provider)}),跳过 LLM 请求处理。"
376
359
  )
360
+ return
377
361
 
378
- if req.conversation:
379
- req.contexts = json.loads(req.conversation.history)
380
-
381
- else:
382
- req = ProviderRequest()
383
- req.prompt = ""
384
- req.image_urls = []
385
- if sel_model := event.get_extra("selected_model"):
386
- req.model = sel_model
387
- if provider_wake_prefix and not event.message_str.startswith(
388
- provider_wake_prefix
389
- ):
390
- return
391
-
392
- req.prompt = event.message_str[len(provider_wake_prefix) :]
393
- # func_tool selection 现在已经转移到 packages/astrbot 插件中进行选择。
394
- # req.func_tool = self.ctx.plugin_manager.context.get_llm_tool_manager()
395
- for comp in event.message_obj.message:
396
- if isinstance(comp, Image):
397
- image_path = await comp.convert_to_file_path()
398
- req.image_urls.append(image_path)
399
-
400
- conversation = await self._get_session_conv(event)
401
- req.conversation = conversation
402
- req.contexts = json.loads(conversation.history)
403
-
404
- event.set_extra("provider_request", req)
405
-
406
- # fix contexts json str
407
- if isinstance(req.contexts, str):
408
- req.contexts = json.loads(req.contexts)
409
-
410
- # apply file extract
411
- if self.file_extract_enabled:
412
- try:
413
- await self._apply_file_extract(event, req)
414
- except Exception as e:
415
- logger.error(f"Error occurred while applying file extract: {e}")
362
+ streaming_response = self.streaming_response
363
+ if (enable_streaming := event.get_extra("enable_streaming")) is not None:
364
+ streaming_response = bool(enable_streaming)
365
+
366
+ logger.debug("ready to request llm provider")
367
+ async with session_lock_manager.acquire_lock(event.unified_msg_origin):
368
+ logger.debug("acquired session lock for llm request")
369
+ if event.get_extra("provider_request"):
370
+ req = event.get_extra("provider_request")
371
+ assert isinstance(req, ProviderRequest), (
372
+ "provider_request 必须是 ProviderRequest 类型。"
373
+ )
416
374
 
417
- if not req.prompt and not req.image_urls:
418
- return
375
+ if req.conversation:
376
+ req.contexts = json.loads(req.conversation.history)
419
377
 
420
- # call event hook
421
- if await call_event_hook(event, EventType.OnLLMRequestEvent, req):
422
- return
378
+ else:
379
+ req = ProviderRequest()
380
+ req.prompt = ""
381
+ req.image_urls = []
382
+ if sel_model := event.get_extra("selected_model"):
383
+ req.model = sel_model
384
+ if provider_wake_prefix and not event.message_str.startswith(
385
+ provider_wake_prefix
386
+ ):
387
+ return
388
+
389
+ req.prompt = event.message_str[len(provider_wake_prefix) :]
390
+ # func_tool selection 现在已经转移到 astrbot/builtin_stars/astrbot 插件中进行选择。
391
+ # req.func_tool = self.ctx.plugin_manager.context.get_llm_tool_manager()
392
+ for comp in event.message_obj.message:
393
+ if isinstance(comp, Image):
394
+ image_path = await comp.convert_to_file_path()
395
+ req.image_urls.append(image_path)
396
+
397
+ conversation = await self._get_session_conv(event)
398
+ req.conversation = conversation
399
+ req.contexts = json.loads(conversation.history)
400
+
401
+ event.set_extra("provider_request", req)
402
+
403
+ # fix contexts json str
404
+ if isinstance(req.contexts, str):
405
+ req.contexts = json.loads(req.contexts)
406
+
407
+ # apply file extract
408
+ if self.file_extract_enabled:
409
+ try:
410
+ await self._apply_file_extract(event, req)
411
+ except Exception as e:
412
+ logger.error(f"Error occurred while applying file extract: {e}")
413
+
414
+ if not req.prompt and not req.image_urls:
415
+ return
423
416
 
424
- # apply knowledge base feature
425
- await self._apply_kb(event, req)
417
+ # call event hook
418
+ if await call_event_hook(event, EventType.OnLLMRequestEvent, req):
419
+ return
426
420
 
427
- # truncate contexts to fit max length
428
- if req.contexts:
429
- req.contexts = self._truncate_contexts(req.contexts)
430
- self._fix_messages(req.contexts)
421
+ # apply knowledge base feature
422
+ await self._apply_kb(event, req)
431
423
 
432
- # session_id
433
- if not req.session_id:
434
- req.session_id = event.unified_msg_origin
424
+ # truncate contexts to fit max length
425
+ if req.contexts:
426
+ req.contexts = self._truncate_contexts(req.contexts)
427
+ self._fix_messages(req.contexts)
435
428
 
436
- # check provider modalities, if provider does not support image/tool_use, clear them in request.
437
- self._modalities_fix(provider, req)
429
+ # session_id
430
+ if not req.session_id:
431
+ req.session_id = event.unified_msg_origin
438
432
 
439
- # filter tools, only keep tools from this pipeline's selected plugins
440
- self._plugin_tool_fix(event, req)
433
+ # check provider modalities, if provider does not support image/tool_use, clear them in request.
434
+ self._modalities_fix(provider, req)
441
435
 
442
- stream_to_general = (
443
- self.unsupported_streaming_strategy == "turn_off"
444
- and not event.platform_meta.support_streaming_message
445
- )
446
- # 备份 req.contexts
447
- backup_contexts = copy.deepcopy(req.contexts)
436
+ # filter tools, only keep tools from this pipeline's selected plugins
437
+ self._plugin_tool_fix(event, req)
448
438
 
449
- # run agent
450
- agent_runner = AgentRunner()
451
- logger.debug(
452
- f"handle provider[id: {provider.provider_config['id']}] request: {req}",
453
- )
454
- astr_agent_ctx = AstrAgentContext(
455
- context=self.ctx.plugin_manager.context,
456
- event=event,
457
- )
458
- await agent_runner.reset(
459
- provider=provider,
460
- request=req,
461
- run_context=AgentContextWrapper(
462
- context=astr_agent_ctx,
463
- tool_call_timeout=self.tool_call_timeout,
464
- ),
465
- tool_executor=FunctionToolExecutor(),
466
- agent_hooks=MAIN_AGENT_HOOKS,
467
- streaming=streaming_response,
468
- )
439
+ stream_to_general = (
440
+ self.unsupported_streaming_strategy == "turn_off"
441
+ and not event.platform_meta.support_streaming_message
442
+ )
443
+ # 备份 req.contexts
444
+ backup_contexts = copy.deepcopy(req.contexts)
469
445
 
470
- if streaming_response and not stream_to_general:
471
- # 流式响应
472
- event.set_result(
473
- MessageEventResult()
474
- .set_result_content_type(ResultContentType.STREAMING_RESULT)
475
- .set_async_stream(
476
- run_agent(
477
- agent_runner,
478
- self.max_step,
479
- self.show_tool_use,
480
- show_reasoning=self.show_reasoning,
481
- ),
446
+ # run agent
447
+ agent_runner = AgentRunner()
448
+ logger.debug(
449
+ f"handle provider[id: {provider.provider_config['id']}] request: {req}",
450
+ )
451
+ astr_agent_ctx = AstrAgentContext(
452
+ context=self.ctx.plugin_manager.context,
453
+ event=event,
454
+ )
455
+ await agent_runner.reset(
456
+ provider=provider,
457
+ request=req,
458
+ run_context=AgentContextWrapper(
459
+ context=astr_agent_ctx,
460
+ tool_call_timeout=self.tool_call_timeout,
482
461
  ),
462
+ tool_executor=FunctionToolExecutor(),
463
+ agent_hooks=MAIN_AGENT_HOOKS,
464
+ streaming=streaming_response,
483
465
  )
484
- yield
485
- if agent_runner.done():
486
- if final_llm_resp := agent_runner.get_final_llm_resp():
487
- if final_llm_resp.completion_text:
488
- chain = (
489
- MessageChain()
490
- .message(final_llm_resp.completion_text)
491
- .chain
492
- )
493
- elif final_llm_resp.result_chain:
494
- chain = final_llm_resp.result_chain.chain
495
- else:
496
- chain = MessageChain().chain
497
- event.set_result(
498
- MessageEventResult(
499
- chain=chain,
500
- result_content_type=ResultContentType.STREAMING_FINISH,
466
+
467
+ if streaming_response and not stream_to_general:
468
+ # 流式响应
469
+ event.set_result(
470
+ MessageEventResult()
471
+ .set_result_content_type(ResultContentType.STREAMING_RESULT)
472
+ .set_async_stream(
473
+ run_agent(
474
+ agent_runner,
475
+ self.max_step,
476
+ self.show_tool_use,
477
+ show_reasoning=self.show_reasoning,
501
478
  ),
502
- )
503
- else:
504
- async for _ in run_agent(
505
- agent_runner,
506
- self.max_step,
507
- self.show_tool_use,
508
- stream_to_general,
509
- show_reasoning=self.show_reasoning,
510
- ):
479
+ ),
480
+ )
511
481
  yield
482
+ if agent_runner.done():
483
+ if final_llm_resp := agent_runner.get_final_llm_resp():
484
+ if final_llm_resp.completion_text:
485
+ chain = (
486
+ MessageChain()
487
+ .message(final_llm_resp.completion_text)
488
+ .chain
489
+ )
490
+ elif final_llm_resp.result_chain:
491
+ chain = final_llm_resp.result_chain.chain
492
+ else:
493
+ chain = MessageChain().chain
494
+ event.set_result(
495
+ MessageEventResult(
496
+ chain=chain,
497
+ result_content_type=ResultContentType.STREAMING_FINISH,
498
+ ),
499
+ )
500
+ else:
501
+ async for _ in run_agent(
502
+ agent_runner,
503
+ self.max_step,
504
+ self.show_tool_use,
505
+ stream_to_general,
506
+ show_reasoning=self.show_reasoning,
507
+ ):
508
+ yield
509
+
510
+ # 恢复备份的 contexts
511
+ req.contexts = backup_contexts
512
+
513
+ await self._save_to_history(
514
+ event,
515
+ req,
516
+ agent_runner.get_final_llm_resp(),
517
+ agent_runner.run_context.messages,
518
+ )
512
519
 
513
- # 恢复备份的 contexts
514
- req.contexts = backup_contexts
515
-
516
- await self._save_to_history(event, req, agent_runner.get_final_llm_resp())
520
+ # 异步处理 WebChat 特殊情况
521
+ if event.get_platform_name() == "webchat":
522
+ asyncio.create_task(self._handle_webchat(event, req, provider))
517
523
 
518
- # 异步处理 WebChat 特殊情况
519
- if event.get_platform_name() == "webchat":
520
- asyncio.create_task(self._handle_webchat(event, req, provider))
524
+ asyncio.create_task(
525
+ Metric.upload(
526
+ llm_tick=1,
527
+ model_name=agent_runner.provider.get_model(),
528
+ provider_type=agent_runner.provider.meta().type,
529
+ ),
530
+ )
521
531
 
522
- asyncio.create_task(
523
- Metric.upload(
524
- llm_tick=1,
525
- model_name=agent_runner.provider.get_model(),
526
- provider_type=agent_runner.provider.meta().type,
527
- ),
528
- )
532
+ except Exception as e:
533
+ logger.error(f"Error occurred while processing agent: {e}")
534
+ await event.send(
535
+ MessageChain().message(
536
+ f"Error occurred while processing agent request: {e}"
537
+ )
538
+ )