AstrBot 4.9.2__py3-none-any.whl → 4.10.0a1__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.
- astrbot/cli/__init__.py +1 -1
- astrbot/core/agent/message.py +6 -4
- astrbot/core/agent/response.py +22 -1
- astrbot/core/agent/run_context.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +54 -15
- astrbot/core/astr_agent_context.py +3 -1
- astrbot/core/astr_agent_run_util.py +23 -2
- astrbot/core/config/default.py +127 -184
- astrbot/core/core_lifecycle.py +3 -0
- astrbot/core/db/__init__.py +72 -0
- astrbot/core/db/po.py +59 -0
- astrbot/core/db/sqlite.py +240 -0
- astrbot/core/message/components.py +4 -5
- astrbot/core/pipeline/respond/stage.py +1 -1
- astrbot/core/platform/sources/telegram/tg_event.py +9 -0
- astrbot/core/platform/sources/webchat/webchat_event.py +22 -18
- astrbot/core/provider/entities.py +41 -0
- astrbot/core/provider/manager.py +203 -93
- astrbot/core/provider/sources/anthropic_source.py +55 -11
- astrbot/core/provider/sources/gemini_source.py +68 -33
- astrbot/core/provider/sources/openai_source.py +21 -6
- astrbot/core/star/command_management.py +449 -0
- astrbot/core/star/context.py +4 -0
- astrbot/core/star/filter/command.py +1 -0
- astrbot/core/star/filter/command_group.py +1 -0
- astrbot/core/star/star_handler.py +4 -0
- astrbot/core/star/star_manager.py +2 -0
- astrbot/core/utils/llm_metadata.py +63 -0
- astrbot/core/utils/migra_helper.py +93 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +56 -13
- astrbot/dashboard/routes/command.py +82 -0
- astrbot/dashboard/routes/config.py +291 -33
- astrbot/dashboard/routes/stat.py +96 -0
- astrbot/dashboard/routes/tools.py +20 -4
- astrbot/dashboard/server.py +1 -0
- {astrbot-4.9.2.dist-info → astrbot-4.10.0a1.dist-info}/METADATA +2 -2
- {astrbot-4.9.2.dist-info → astrbot-4.10.0a1.dist-info}/RECORD +41 -38
- {astrbot-4.9.2.dist-info → astrbot-4.10.0a1.dist-info}/WHEEL +0 -0
- {astrbot-4.9.2.dist-info → astrbot-4.10.0a1.dist-info}/entry_points.txt +0 -0
- {astrbot-4.9.2.dist-info → astrbot-4.10.0a1.dist-info}/licenses/LICENSE +0 -0
astrbot/cli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "4.
|
|
1
|
+
__version__ = "4.10.0-alpha.1"
|
astrbot/core/agent/message.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from typing import Any, ClassVar, Literal, cast
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, GetCoreSchemaHandler, model_validator
|
|
6
|
+
from pydantic import BaseModel, GetCoreSchemaHandler, model_serializer, model_validator
|
|
7
7
|
from pydantic_core import core_schema
|
|
8
8
|
|
|
9
9
|
|
|
@@ -122,10 +122,12 @@ class ToolCall(BaseModel):
|
|
|
122
122
|
extra_content: dict[str, Any] | None = None
|
|
123
123
|
"""Extra metadata for the tool call."""
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
@model_serializer(mode="wrap")
|
|
126
|
+
def serialize(self, handler):
|
|
127
|
+
data = handler(self)
|
|
126
128
|
if self.extra_content is None:
|
|
127
|
-
|
|
128
|
-
return
|
|
129
|
+
data.pop("extra_content", None)
|
|
130
|
+
return data
|
|
129
131
|
|
|
130
132
|
|
|
131
133
|
class ToolCallPart(BaseModel):
|
astrbot/core/agent/response.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import typing as T
|
|
2
|
-
from dataclasses import dataclass
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
3
|
|
|
4
4
|
from astrbot.core.message.message_event_result import MessageChain
|
|
5
|
+
from astrbot.core.provider.entities import TokenUsage
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class AgentResponseData(T.TypedDict):
|
|
@@ -12,3 +13,23 @@ class AgentResponseData(T.TypedDict):
|
|
|
12
13
|
class AgentResponse:
|
|
13
14
|
type: str
|
|
14
15
|
data: AgentResponseData
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class AgentStats:
|
|
20
|
+
token_usage: TokenUsage = field(default_factory=TokenUsage)
|
|
21
|
+
start_time: float = 0.0
|
|
22
|
+
end_time: float = 0.0
|
|
23
|
+
time_to_first_token: float = 0.0
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def duration(self) -> float:
|
|
27
|
+
return self.end_time - self.start_time
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict:
|
|
30
|
+
return {
|
|
31
|
+
"token_usage": self.token_usage.__dict__,
|
|
32
|
+
"start_time": self.start_time,
|
|
33
|
+
"end_time": self.end_time,
|
|
34
|
+
"time_to_first_token": self.time_to_first_token,
|
|
35
|
+
}
|
|
@@ -9,7 +9,7 @@ from .message import Message
|
|
|
9
9
|
TContext = TypeVar("TContext", default=Any)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@dataclass
|
|
12
|
+
@dataclass
|
|
13
13
|
class ContextWrapper(Generic[TContext]):
|
|
14
14
|
"""A context for running an agent, which can be used to pass additional data or state."""
|
|
15
15
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import time
|
|
2
3
|
import traceback
|
|
3
4
|
import typing as T
|
|
4
5
|
|
|
@@ -12,6 +13,7 @@ from mcp.types import (
|
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
from astrbot import logger
|
|
16
|
+
from astrbot.core.message.components import Json
|
|
15
17
|
from astrbot.core.message.message_event_result import (
|
|
16
18
|
MessageChain,
|
|
17
19
|
)
|
|
@@ -24,7 +26,7 @@ from astrbot.core.provider.provider import Provider
|
|
|
24
26
|
|
|
25
27
|
from ..hooks import BaseAgentRunHooks
|
|
26
28
|
from ..message import AssistantMessageSegment, Message, ToolCallMessageSegment
|
|
27
|
-
from ..response import AgentResponseData
|
|
29
|
+
from ..response import AgentResponseData, AgentStats
|
|
28
30
|
from ..run_context import ContextWrapper, TContext
|
|
29
31
|
from ..tool_executor import BaseFunctionToolExecutor
|
|
30
32
|
from .base import AgentResponse, AgentState, BaseAgentRunner
|
|
@@ -69,6 +71,9 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
69
71
|
)
|
|
70
72
|
self.run_context.messages = messages
|
|
71
73
|
|
|
74
|
+
self.stats = AgentStats()
|
|
75
|
+
self.stats.start_time = time.time()
|
|
76
|
+
|
|
72
77
|
async def _iter_llm_responses(self) -> T.AsyncGenerator[LLMResponse, None]:
|
|
73
78
|
"""Yields chunks *and* a final LLMResponse."""
|
|
74
79
|
if self.streaming:
|
|
@@ -98,6 +103,10 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
98
103
|
|
|
99
104
|
async for llm_response in self._iter_llm_responses():
|
|
100
105
|
if llm_response.is_chunk:
|
|
106
|
+
# update ttft
|
|
107
|
+
if self.stats.time_to_first_token == 0:
|
|
108
|
+
self.stats.time_to_first_token = time.time() - self.stats.start_time
|
|
109
|
+
|
|
101
110
|
if llm_response.result_chain:
|
|
102
111
|
yield AgentResponse(
|
|
103
112
|
type="streaming_delta",
|
|
@@ -121,6 +130,10 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
121
130
|
)
|
|
122
131
|
continue
|
|
123
132
|
llm_resp_result = llm_response
|
|
133
|
+
|
|
134
|
+
if not llm_response.is_chunk and llm_response.usage:
|
|
135
|
+
# only count the token usage of the final response for computation purpose
|
|
136
|
+
self.stats.token_usage += llm_response.usage
|
|
124
137
|
break # got final response
|
|
125
138
|
|
|
126
139
|
if not llm_resp_result:
|
|
@@ -132,6 +145,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
132
145
|
if llm_resp.role == "err":
|
|
133
146
|
# 如果 LLM 响应错误,转换到错误状态
|
|
134
147
|
self.final_llm_resp = llm_resp
|
|
148
|
+
self.stats.end_time = time.time()
|
|
135
149
|
self._transition_state(AgentState.ERROR)
|
|
136
150
|
yield AgentResponse(
|
|
137
151
|
type="err",
|
|
@@ -146,6 +160,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
146
160
|
# 如果没有工具调用,转换到完成状态
|
|
147
161
|
self.final_llm_resp = llm_resp
|
|
148
162
|
self._transition_state(AgentState.DONE)
|
|
163
|
+
self.stats.end_time = time.time()
|
|
149
164
|
# record the final assistant message
|
|
150
165
|
self.run_context.messages.append(
|
|
151
166
|
Message(
|
|
@@ -175,22 +190,19 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
175
190
|
# 如果有工具调用,还需处理工具调用
|
|
176
191
|
if llm_resp.tools_call_name:
|
|
177
192
|
tool_call_result_blocks = []
|
|
178
|
-
for tool_call_name in llm_resp.tools_call_name:
|
|
179
|
-
yield AgentResponse(
|
|
180
|
-
type="tool_call",
|
|
181
|
-
data=AgentResponseData(
|
|
182
|
-
chain=MessageChain(type="tool_call").message(
|
|
183
|
-
f"🔨 调用工具: {tool_call_name}"
|
|
184
|
-
),
|
|
185
|
-
),
|
|
186
|
-
)
|
|
187
193
|
async for result in self._handle_function_tools(self.req, llm_resp):
|
|
188
194
|
if isinstance(result, list):
|
|
189
195
|
tool_call_result_blocks = result
|
|
190
196
|
elif isinstance(result, MessageChain):
|
|
191
|
-
result.type
|
|
197
|
+
if result.type is None:
|
|
198
|
+
# should not happen
|
|
199
|
+
continue
|
|
200
|
+
if result.type == "tool_direct_result":
|
|
201
|
+
ar_type = "tool_call_result"
|
|
202
|
+
else:
|
|
203
|
+
ar_type = result.type
|
|
192
204
|
yield AgentResponse(
|
|
193
|
-
type=
|
|
205
|
+
type=ar_type,
|
|
194
206
|
data=AgentResponseData(chain=result),
|
|
195
207
|
)
|
|
196
208
|
# 将结果添加到上下文中
|
|
@@ -233,6 +245,19 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
233
245
|
llm_response.tools_call_args,
|
|
234
246
|
llm_response.tools_call_ids,
|
|
235
247
|
):
|
|
248
|
+
yield MessageChain(
|
|
249
|
+
type="tool_call",
|
|
250
|
+
chain=[
|
|
251
|
+
Json(
|
|
252
|
+
data={
|
|
253
|
+
"id": func_tool_id,
|
|
254
|
+
"name": func_tool_name,
|
|
255
|
+
"args": func_tool_args,
|
|
256
|
+
"ts": time.time(),
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
],
|
|
260
|
+
)
|
|
236
261
|
try:
|
|
237
262
|
if not req.func_tool:
|
|
238
263
|
return
|
|
@@ -306,7 +331,6 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
306
331
|
content=res.content[0].text,
|
|
307
332
|
),
|
|
308
333
|
)
|
|
309
|
-
yield MessageChain().message(res.content[0].text)
|
|
310
334
|
elif isinstance(res.content[0], ImageContent):
|
|
311
335
|
tool_call_result_blocks.append(
|
|
312
336
|
ToolCallMessageSegment(
|
|
@@ -328,7 +352,6 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
328
352
|
content=resource.text,
|
|
329
353
|
),
|
|
330
354
|
)
|
|
331
|
-
yield MessageChain().message(resource.text)
|
|
332
355
|
elif (
|
|
333
356
|
isinstance(resource, BlobResourceContents)
|
|
334
357
|
and resource.mimeType
|
|
@@ -352,7 +375,22 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
352
375
|
content="返回的数据类型不受支持",
|
|
353
376
|
),
|
|
354
377
|
)
|
|
355
|
-
|
|
378
|
+
|
|
379
|
+
# yield the last tool call result
|
|
380
|
+
if tool_call_result_blocks:
|
|
381
|
+
last_tcr_content = str(tool_call_result_blocks[-1].content)
|
|
382
|
+
yield MessageChain(
|
|
383
|
+
type="tool_call_result",
|
|
384
|
+
chain=[
|
|
385
|
+
Json(
|
|
386
|
+
data={
|
|
387
|
+
"id": func_tool_id,
|
|
388
|
+
"ts": time.time(),
|
|
389
|
+
"result": last_tcr_content,
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
],
|
|
393
|
+
)
|
|
356
394
|
|
|
357
395
|
elif resp is None:
|
|
358
396
|
# Tool 直接请求发送消息给用户
|
|
@@ -362,6 +400,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
362
400
|
f"{func_tool_name} 没有没有返回值或者将结果直接发送给用户,此工具调用不会被记录到历史中。"
|
|
363
401
|
)
|
|
364
402
|
self._transition_state(AgentState.DONE)
|
|
403
|
+
self.stats.end_time = time.time()
|
|
365
404
|
else:
|
|
366
405
|
# 不应该出现其他类型
|
|
367
406
|
logger.warning(
|
|
@@ -6,8 +6,10 @@ from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
|
|
6
6
|
from astrbot.core.star.context import Context
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@dataclass
|
|
9
|
+
@dataclass
|
|
10
10
|
class AstrAgentContext:
|
|
11
|
+
__pydantic_config__ = {"arbitrary_types_allowed": True}
|
|
12
|
+
|
|
11
13
|
context: Context
|
|
12
14
|
"""The star context instance"""
|
|
13
15
|
event: AstrMessageEvent
|
|
@@ -4,6 +4,7 @@ from collections.abc import AsyncGenerator
|
|
|
4
4
|
from astrbot.core import logger
|
|
5
5
|
from astrbot.core.agent.runners.tool_loop_agent_runner import ToolLoopAgentRunner
|
|
6
6
|
from astrbot.core.astr_agent_context import AstrAgentContext
|
|
7
|
+
from astrbot.core.message.components import Json
|
|
7
8
|
from astrbot.core.message.message_event_result import (
|
|
8
9
|
MessageChain,
|
|
9
10
|
MessageEventResult,
|
|
@@ -33,16 +34,27 @@ async def run_agent(
|
|
|
33
34
|
msg_chain = resp.data["chain"]
|
|
34
35
|
if msg_chain.type == "tool_direct_result":
|
|
35
36
|
# tool_direct_result 用于标记 llm tool 需要直接发送给用户的内容
|
|
36
|
-
await astr_event.send(
|
|
37
|
+
await astr_event.send(msg_chain)
|
|
37
38
|
continue
|
|
39
|
+
if astr_event.get_platform_id() == "webchat":
|
|
40
|
+
await astr_event.send(msg_chain)
|
|
38
41
|
# 对于其他情况,暂时先不处理
|
|
39
42
|
continue
|
|
40
43
|
elif resp.type == "tool_call":
|
|
41
44
|
if agent_runner.streaming:
|
|
42
45
|
# 用来标记流式响应需要分节
|
|
43
46
|
yield MessageChain(chain=[], type="break")
|
|
44
|
-
|
|
47
|
+
|
|
48
|
+
if astr_event.get_platform_name() == "webchat":
|
|
45
49
|
await astr_event.send(resp.data["chain"])
|
|
50
|
+
elif show_tool_use:
|
|
51
|
+
json_comp = resp.data["chain"].chain[0]
|
|
52
|
+
if isinstance(json_comp, Json):
|
|
53
|
+
m = f"🔨 调用工具: {json_comp.data.get('name')}"
|
|
54
|
+
else:
|
|
55
|
+
m = "🔨 调用工具..."
|
|
56
|
+
chain = MessageChain(type="tool_call").message(m)
|
|
57
|
+
await astr_event.send(chain)
|
|
46
58
|
continue
|
|
47
59
|
|
|
48
60
|
if stream_to_general and resp.type == "streaming_delta":
|
|
@@ -69,6 +81,15 @@ async def run_agent(
|
|
|
69
81
|
continue
|
|
70
82
|
yield resp.data["chain"] # MessageChain
|
|
71
83
|
if agent_runner.done():
|
|
84
|
+
# send agent stats to webchat
|
|
85
|
+
if astr_event.get_platform_name() == "webchat":
|
|
86
|
+
await astr_event.send(
|
|
87
|
+
MessageChain(
|
|
88
|
+
type="agent_stats",
|
|
89
|
+
chain=[Json(data=agent_runner.stats.to_dict())],
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
|
|
72
93
|
break
|
|
73
94
|
|
|
74
95
|
except Exception as e:
|