乐于分享
好东西不私藏

AgentScope 2.0 源码解析系列第 十二 篇 state 模块|一个 Pydantic model 接住了 Agent 的全部运行时状态

AgentScope 2.0 源码解析系列第 十二 篇 state 模块|一个 Pydantic model 接住了 Agent 的全部运行时状态

AgentScope 2.0 源码解析系列第 十二 篇 state 模块

一个 Pydantic model 接住了 Agent 的全部运行时状态

本文是 AgentScope 2.0 源码解析系列 第 12 篇 / 共 12 篇(state 模块)。

已发篇目(按依赖顺序,附一句话结论):

- 第 1 篇 Agent:用一套机制消化 LLM 输出和工具执行的双重不确定性

- 第 2 篇 Event:26 种事件类型是整个框架流式输出、中间件拦截、协议转换的通信骨架

- 第 3 篇 Message:把 Agent 之间流动的所有信息统一成 Msg + ContentBlock

- 第 4 篇 Model:用模板方法 + 钩子把九家大模型 API 归一成一个可调用对象

- 第 5 篇 Tool:Python 函数、MCP 工具、内置工具全部归一到 ToolBase 一个协议

- 第 6 篇 Permission:用一个 bypass_immune 布尔位在数据层面给安全栅栏划界

- 第 7 篇 Middleware:把"定制 Agent"从继承重写改成挂中间件,6 个 Hook 接住所有变化点

- 第 8 篇 RAG:5 阶段流水线 + 每阶段一个不变量数据类型 + Section 硬边界防越界

- 第 9 篇 Workspace:把"在哪儿执行"和"执行什么"解耦,网络可达性难题关进进程内网关

- 第 10 篇 MCP:一个布尔位 + 四个正交生命周期方法统一三套传输、两种用法

- 第 11 篇 Skill:把"什么是 skill"抽象成最小接口,脏活全部下沉到 workspace 层

第 12 篇 state(本文):一个 Pydantic model + 四个分桶 context,接住 Agent 的全部运行时状态

下一篇:可能是 formatter(消息格式化适配层)或 app 层(Web 服务与调度)。关注追更,系列更完后会整理总目录。

📌 读完这篇你会带走这些源码层面的结论:
  • state 模块只有 1 个核心类 AgentStatestate/_state.py:149),是 BaseModel,不是普通 class——这一个选择让它天然支持状态持久化(存盘/恢复/克隆),派生子 Agent 时直接构造一个新实例
  • 全部状态按关注点分四个桶permission_context / tool_context / tasks_context / middle_context 各自独立——权限规则不污染工具缓存、中间件状态不进持久化主路径
  • 双 ID 体系贯穿会话状态始终:session_id(会话级,跨 reply 不变)+ reply_id(单轮级,_agent.py:732 每轮 _generate_id() 重新生成)——append_context 靠 reply_id 判断"要不要复用尾消息",这是流式增量写入的开关
  • context + summary 双轨喂 LLM:context 是未压缩原文、summary 是压缩摘要,_call_model_agent.py:2293)把 summary 作为 user 消息拼在最前——这承接第 7 篇 Middleware 的 on_compress_context
  • middle_context 是个 dict[str, Any] 的逃生舱口:中间件靠 middleware_key 自分桶,第 7 篇的 ReplyBudgetControlMiddleware 用 middle_context[middleware_key][reply_id] 二级结构存每轮预算——实例无状态、跨 Agent 可共享
  • state 模块本身不负责持久化——它只是个可序列化 model,存盘/恢复由上层 app 的存储层(app/storage/_model/_session.py)做,职责清晰分离

适合谁读:在搭需要会话恢复、多 Agent 状态隔离、或想给 Agent 加自定义运行时状态的 Agent 开发者。

预计阅读:主线约 8 分钟(字段表较多,附录字段表另需 4 分钟)。

💡 如果只记一件事

state 模块用「一个 Pydantic model + 按关注点分桶的子 context」接住了 Agent 的全部运行时状态——每个关注点(权限/工具/任务/中间件)一个独立子 model,互不污染,整体又是一个可序列化的 BaseModel 天然支持存盘恢复。这套「按关注点分桶的可组合状态」思路,搬到任何需要"多维度运行时状态且要持久化"的服务都成立——你的应用状态不该是一个 God Object,而该是几个职责单一的小 model 组合。


一、这个模块到底在解决什么问题?

前面十一篇里,self.state.xxx 出现过无数次:

  • 第 1 篇讲 Agent 的 ReAct 循环,输入是 self.state.context
  • 第 6 篇讲权限,AgentState.permission_context 挂在 state 上(_state.py:170
  • 第 7 篇讲中间件,预算中间件把每轮花费塞进 self.state.middle_context
  • 第 5 篇讲工具,Read/Edit 读 self.state.tool_context 的缓存

没有一篇正面讲过 state 模块本身。读者跟着读了十一篇,对 self.state 的认知是碎片化的。这一篇就来填这个贯穿全系列的缺口。

state 模块(src/agentscope/state/,只有 3 个文件、约 250 行)要回答的核心问题是:

一个 Agent 跑起来,它的"运行时状态"到底长什么样、怎么组织、为什么这么组织?

"运行时状态"这个词听起来抽象,拆开看至少包括:

  1. 对话历史:用户和 Agent 说了什么(要喂给 LLM)
  2. 压缩摘要:历史太长时压缩出来的 summary(也要喂给 LLM)
  3. 当前轮次:ReAct 循环走到第几轮、这轮的 reply 编号
  4. 权限规则:用户配的 allow/deny/ask 规则、当前模式
  5. 工具缓存:Read 过的文件内容(避免重复读盘)、激活的工具组
  6. 任务列表:Agent 正在追踪的 todo 任务
  7. 中间件状态:预算中间件记的累计花费、其它中间件的跨轮状态

如果把这些全塞进一个扁平的大对象,会得到一个上帝对象(God Object):权限改一下可能影响工具缓存、中间件状态混进持久化主路径、字段越加越多没人知道边界在哪。

state 模块的做法是:一个 AgentState 总 model,下面按关注点分四个子 context,每个子 context 是一个独立的 BaseModel,职责单一、互不污染。整个 AgentState 自己也是个 BaseModel,一行 model_dump() 就能存盘,一行 model_validate(...) 就能恢复。

一句话概括:state 模块用"组合优于继承/平铺"的方式,把 Agent 的全部运行时状态组织成一个可持久化、可克隆、关注点分离的 Pydantic model。

二、这个模块在整个框架中的位置

先看它和谁打交道:

输入:Agent 构造时拿到一个 AgentState 实例(默认空,或从存储层恢复),运行过程中所有模块往里面读写。

输出:无显式输出——它是被持有、被读写的状态容器。

它依赖谁(上游):

  • messagecontext 是 list[Msg]summary 也能装 TextBlock | DataBlock
  • permissionpermission_context: PermissionContext 直接内嵌(第 6 篇的核心数据结构)

谁依赖它(下游):这是 state 模块的关键特征——几乎整个框架都依赖它。实测 grep "AgentState" 命中 src 内 25 个文件,是除 Msg 外被引用最多的类型。主要消费方:

消费方
用到的字段
典型操作
agent/_agent.py
几乎全部
读 context/summary 喂 LLM、轮换 reply_id、调 append_context/has_awaiting_tool_calls
tool/_builtin/_read.py
_edit.py
tool_contextget_cache
/cache_file(文件读缓存)
tool/_toolkit.pytool_context.activated_groups
按激活的工具组过滤可用工具
middleware/_budget.pymiddle_context
存每轮预算花费(第 7 篇的 ReplyBudgetControlMiddleware)
middleware/_rag.py
_longterm_memory/...
append_context
中间件在 on_reasoning 钩子里往 context 追加 block
app/_session.py
storage/_model/_session.py
整个 AgentState
存盘/恢复/派生子 Agent

注意最后一行——state 模块自己不持久化。它提供"可序列化"的能力(因为是 BaseModel),但"什么时候存、存到哪"是上层 app 的存储层决定的。这种职责分离是 state 模块能保持极简的关键:它不碰文件系统、不碰数据库,只管"我是个能被序列化的状态容器"。

三、为什么这样设计?

state 模块在每个关键岔路口都做了克制的选择。

决策 1:用 Pydantic BaseModel,而不是普通 class

AgentState_state.py:149)继承 BaseModel,所有字段都是带类型的 Pydantic 字段。这一个选择带来三个好处:

  • 天然序列化model_dump() 一行转 dict、model_validate(data) 一行从 dict 恢复。存盘/加载/跨进程传输零额外代码
  • 派生子 Agent 时直接构造:app 层的调度器(app/_manager/_scheduler/_scheduler_manager.py)给定时任务子 Agent 构造新的 AgentState,直接传字段即可
  • 类型校验白送:构造时字段类型不对会直接报错,不用手写校验

这和第 6 篇 PermissionContext 用 BaseModel(第 6 篇决策 6)是同一套思路的延续——框架里凡是"需要持久化或跨边界传递的状态对象",一律用 Pydantic model。

决策 2:按关注点分桶,而不是平铺

这是 state 模块最值得学的点。看 AgentState 的字段定义(_state.py:149-192),用注释明确分了四桶:

class AgentState(BaseModel):    # 对话主轴    session_id: str = ...summary: str | list[TextBlock | DataBlock] = ""    context: list[Msg] = ...    reply_id: str = ...    cur_iter: int = 0    # === 分桶 1: 权限 ===permission_context: PermissionContext = ...    # === 分桶 2: 工具 ===tool_context: ToolContext = ...    # === 分桶 3: 任务 === tasks_context: TaskContext = ...    # === 分桶 4: 中间件(逃生舱口)===    middle_context: dict[str, Any] = ...

前三桶(权限/工具/任务)是强类型子 model——PermissionContext(第 6 篇讲过)、ToolContextTaskContext 各自是独立的 BaseModel,字段、方法都封装在自己里面。AgentState 只是持有它们的引用。

为什么不全平铺成一个扁平的大 model?因为关注点会膨胀。今天有权限规则和工具缓存,明天可能加 tracing 状态、审计日志。如果平铺,每加一类状态就在 AgentState 上堆字段,很快变成没人敢动的上帝对象。分桶后,新关注点要么进对应的子 model,要么像 middle_context 那样走逃生舱口(见决策 4),AgentState 本身的字段表保持稳定。

决策 3:双 ID 体系——session_id 和 reply_id

这是理解 ReAct 循环如何往 context 写数据的关键。两个字段默认值都是 _generate_id()_state.py:152/161),但生命周期完全不同:

  • session_id:会话级。一个会话从开始到结束不变,用来标识"这是哪次对话"。存盘恢复时,同一个 session 的 state 共享同一个 session_id
  • reply_id:单轮级。每轮 reply 开始时重新生成_agent.py:732self.state.reply_id = _generate_id()

reply_id 的作用藏在 append_context_state.py:194-222)里。这个方法是 Agent 流式写入 context 的主入口,它的逻辑是:

def append_context(self, name, blocks):    # 如果最后一条消息是"我自己本轮写的"(name 相同 + id == 当前 reply_id)    if (        self.contextand self.context[-1].role == "assistant"        and self.context[-1].name == name        and self.context[-1].id == self.reply_id   # ← 关键判断    ):        self.context[-1].content.extend(blocks)    # 复用,往尾消息追加    else:        self.context.append(Msg(id=self.reply_id, ...))  # 否则新建一条

这个"复用尾消息 vs 新建"的判断,是流式增量写入的开关。一轮 reply 里 Agent 会产出多个事件(reasoning、tool_call、tool_result、text),它们应该拼在同一条 assistant 消息里(因为同属一轮回复)。靠 reply_id 相等来识别"还在同一轮",就能把后续 block 追加到前面那条消息上,而不是每来一个 block 新建一条。

cur_iter 是 ReAct 循环的轮次计数器(第 1 篇讲过的思考-行动循环),和 reply_id 是不同维度——一轮 reply 内部可能经历多个思考-行动 iteration。

决策 4:middle_context 是逃生舱口

前三桶(permission/tool/tasks)是框架预定义的关注点。但中间件是开放的——用户会挂各种自定义中间件,每种都可能想存自己的状态。框架不可能预知所有中间件需要什么状态字段。

middle_context: dict[str, Any]_state.py:190)就是给这种情况留的逃生舱口。它的结构是二级字典:外层 key 是中间件自己的 middleware_key(唯一标识),内层是任意结构。

看第 7 篇的 ReplyBudgetControlMiddleware 怎么用它(middleware/_budget.py:128-143):

if middleware_key not in agent.state.middle_context:    agent.state.middle_context[middleware_key] = {}agent.state.middle_context[middleware_key][event.reply_id] = 0   # 初始化本轮# ...agent.state.middle_context[middleware_key][event.reply_id] += cost  # 累加本轮花费

注意它用 reply_id 做内层 key——这样同一个中间件实例跨多轮 reply 的状态互不干扰。中间件实例本身无状态(第 7 篇强调过这点),所有运行时状态都寄存在 middle_context 里。这让一个中间件实例能安全地被多个 Agent 共享。

逃生舱口的好处是开放扩展、封闭修改:加新中间件不用改 AgentState 的字段表,中间件自己往 middle_context 塞就行。代价是丢失了类型安全(dict[str, Any])——这是刻意的权衡:框架预定义的强类型桶负责"我知道我需要什么状态"的场景,逃生舱口负责"我不知道你会需要什么"的场景。

决策 5:append_context 和 has_awaiting_tool_calls——状态机查询封进 state

AgentState 不只是个数据袋,它还封装了两个基于 context 的状态查询

  • append_context_state.py:194):上面讲过,流式增量写入
  • has_awaiting_tool_calls_state.py:224):查"尾消息里有没有还在等待外部响应的工具调用"

has_awaiting_tool_calls 值得细看,它把第 3 篇的 ToolCallState 状态机和第 5 篇的工具调用流程收口到一起(_state.py:244-250):

return any(    tc.state == ToolCallState.ASKING          # 还在等用户确认    or (      tc.state == ToolCallState.SUBMITTED      # 或提交了外部执行        and tc.id not in result_ids                    # 但还没收到结果    )    for tc in last_msg.get_content_blocks("tool_call"))

这个查询在 _reply_impl_agent.py,codegraph 显示是唯一调用方)里用来判断"这轮回复要不要继续等"。把这种基于 context 的状态判断封进 state 自己,而不是散落在 agent 主循环里,是因为这些判断的依据完全在 state 内部(context 里的 block 状态)——按"信息专家"原则,应该由持有这些数据的对象来回答。

四、跟着我阅读源码

文件优先级(从必读到可跳过):

  1. _state.py(必读)——整个模块的核心,AgentState + 三个子 context 全在这。文章篇幅主要花在这个文件。
  2. _task.py(按需查阅)——Task 数据类,逻辑简单,知道它是个 todo 项即可。
  3. __init__.py——只导出 AgentStateTaskContextTask 三个名字。

第一次读只需精读 AgentState 一张字段表(下面 4.1),ToolContext / TaskContext / Task 三张按需查阅,不影响理解主线。

4.1 AgentState(核心,必读)

定义于 _state.py:149-192。整个框架的运行时状态容器。字段按四桶组织:

字段
类型
默认值
含义
session_idstr_generate_id()
会话级 ID,标识"这是哪次对话",跨 reply 不变
summarystr \| list[TextBlock \| DataBlock]""
压缩摘要,_call_model_agent.py:2293)拼在 context 最前喂 LLM
contextlist[Msg][]
未压缩对话历史,喂 LLM 的主体
reply_idstr_generate_id()
单轮级 ID,_agent.py:732 每轮 reply 重新生成
cur_iterint0
ReAct 循环当前轮次计数
permission_contextPermissionContextPermissionContext()
权限上下文(第 6 篇核心数据结构)
tool_contextToolContextToolContext()
工具缓存 + 激活的工具组
tasks_contextTaskContextTaskContext()
Agent 追踪的 todo 任务
middle_contextdict[str, Any]{}
中间件逃生舱口,按 middleware_key 二级分桶

讲透三个关键字段:

reply_id 是流式写入的开关append_context 靠它判断是追加到尾消息还是新建消息(见决策 3)。一轮 reply 内产出的所有 block 共享同一个 reply_id,因此能拼进同一条 assistant 消息。

summary 和 context 是双轨context 是原始历史,越来越长;summary 是压缩后的摘要。_call_model_agent.py:2293-2295)把它们拼起来喂 LLM——summary 作为一条 user 消息放在最前面。当 context 超长时,第 7 篇的 on_compress_context 钩子会把旧消息压进 summary,腾出 context 空间(_agent.py:536self.state.summary = new_summary)。这是 state 提供数据结构、middleware 提供压缩策略的分工。

middle_context 的二级结构。外层 key 是中间件的 middleware_key(字符串),内层结构由中间件自定义。ReplyBudgetControlMiddleware 用 middle_context[key][reply_id] 存每轮花费(_budget.py:130/143),reply 结束后还会清理(_budget.py:134pop(reply_id))。注意整个 middle_context 是个 dict[str, Any]——这是刻意丢掉类型安全换来的扩展性(见决策 4)。

4.2 ToolContext(按需查阅)

定义于 _state.py:32-139。工具相关的运行时状态,自带方法(和纯数据的 PermissionContext 不同):

字段
类型
默认值
含义
max_cache_filesint100
最多缓存多少个文件
max_cache_bytesfloat25000
缓存累计大小上限(KB)
read_file_cachelist[ReadCacheEntry][]
Read/Write/Edit 的文件读缓存
activated_groupslist[str][]
激活的工具组名(第 5 篇的 ToolGroup)

三个异步方法:

  • get_cache(file_path):46):按 mtime 校验缓存是否过期,过期就剔除返回 None
  • cache_file(file_path, lines):74):带 LRU 淘汰(按文件数和字节数双上限)的缓存写入
  • clean_file_cache(reserved_paths):122):只保留指定路径的缓存,其余剔除——_agent.py:2100 在压缩上下文时调它清掉不再需要的缓存

ReadCacheEntry:23)是缓存条目,字段:lines / updated_at(文件 mtime)/ bytes(KB)/ file_path。缓存有效性靠 mtime 比对(:62updated_at == entry.updated_at),文件被改过就失效——和第 11 篇 Skill 的 mtime 缓存是同一思路。

activated_groups 被 _toolkit.py:250/564 读取,用来过滤"当前哪些工具组可用"。第 5 篇讲过的 ResetTools 元工具就是改这个字段来激活/停用工具组。

4.3 TaskContext(按需查阅)

定义于 _state.py:142-146。极简:

字段
类型
默认值
含义
taskslist[Task][]
Agent 追踪的任务列表

4.4 Task(按需查阅)

定义于 _task.py:11-39。一个 todo 项:

字段
类型
默认值
含义
subjectstr
—(必填)
任务标题
descriptionstr
—(必填)
任务描述
metadatadict[str, Any]
—(必填)
附加元数据
created_atstrdatetime.now().isoformat()
创建时间
stateLiteral["pending","in_progress","completed"]"pending"
任务状态
idstr_generate_id()
任务 ID
ownerstr \| NoneNone
任务所有者
blockslist[str][]
被本任务阻塞的任务 ID
blocked_bylist[str][]
阻塞本任务的任务 ID

blocks / blocked_by 这对字段实现了任务间的依赖关系——这是个轻量的 DAG 雏形,目前由 _task/ 子包的工具操作。

五、代码到底是怎么运行起来的?

用一条具体调用走完全程。场景:Agent 已经跑过几轮,现在用户发了一条新消息,触发新一轮 reply。我们跟着 reply_id 和 context 的变化看 state 怎么被读写。

步骤 0:新一轮 reply 开始,轮换 reply_id

reply → _reply → _reply_impl_agent.py)。在 _reply_impl 里,第一件事就是轮换 reply_id_agent.py:732):

self.state.reply_id = _generate_id()   # 新一轮,新 ID

这一行是双 ID 体系运转的起点。从这一刻起,本轮产出的所有 block 都会带这个新 reply_id

步骤 1:构造喂给 LLM 的输入(读 context + summary)

_call_model_agent.py:2293)读取 state 拼输入:

if self.state.summary:    msgs_system.append(UserMsg(name="user", content=self.state.summary))  # 摘要放最前# ... 然后拼上 self.state.context ...

这里能看到双轨怎么合作:summary 作为一条 user 消息垫在最前,context 跟在后面。如果第 7 篇的压缩中间件之前跑过,summary 里就已经有旧对话的浓缩,context 里只剩较新的消息。

步骤 2:Agent 流式产出,调 append_context 写入

Agent 在 ReAct 循环里产出 reasoning / tool_call / tool_result / text 等事件。这些事件最终通过 append_context_state.py:194)落到 context 里。

第一次调 append_context(比如写入 reasoning block)时,尾消息的 id 还是上一轮的 reply_id(或 context 为空),不等于本轮 reply_id,于是走 else 分支新建一条 assistant 消息:215-222),id 设为本轮 reply_id

本轮后续每次调 append_context,尾消息的 id 已经等于本轮 reply_id 了(:210 命中),于是走 if 分支往这条消息追加 block:212)。这就是一轮 reply 的所有产出拼在同一条消息里的机制。

补充:中间件也能调 append_context。codegraph 显示 append_context 有两个调用方在中间件里——_longterm_memory/_agentic_memory/_middleware.py 和 _rag.py,它们在 on_reasoning 钩子里往 context 追加检索到的记忆/RAG 片段。

步骤 3:工具调用读写 tool_context

如果本轮 Agent 决定调 Read 工具,_read.py:229 会读缓存:

cache = await _agent_state.tool_context.get_cache(file_path)   # mtime 校验

命中就直接用缓存的 lines,不命中就读盘后 cache_file_read.py:244)写回缓存。Edit 工具(_edit.py:298)也走同样的缓存。

工具组过滤发生在 _toolkit.py:250——读 activated_groups 决定哪些工具对 LLM 可见。

步骤 4:预算中间件读写 middle_context

如果挂了 ReplyBudgetControlMiddleware(第 7 篇),它会在 on_model_call 钩子里累计花费(_budget.py:143):

agent.state.middle_context[middleware_key][event.reply_id] += cost

预算耗尽时,它把传给下游的 tool_choice 改成 none(第 7 篇讲过的 execute_chain 改写参数),阻止后续工具调用。reply 结束后清理本轮条目(_budget.py:134)。

步骤 5:判断要不要继续等——has_awaiting_tool_calls

如果本轮有工具调用进入了 ASKING(等用户确认)或 SUBMITTED(外部执行未回),_reply_impl 调 has_awaiting_tool_calls_state.py:224)。返回 True 时,Agent 本轮就此挂起,等外部信号回来再续。

步骤 6:持久化(不在 state 模块内)

一轮 reply 结束后,存盘由上层 app 的存储层做(app/_service/_session.pystorage/_model/_session.py)——它调 AgentState 的序列化能力(BaseModel 自带)把整个 state 落库。state 模块本身不感知"我被存了"。

六、如何开始调试源码

如果你要调试 Agent 的状态相关问题,按这个顺序下断点:

第一次:看 reply_id 有没有正确轮换

断点 _agent.py:732self.state.reply_id = _generate_id())。如果 append_context 一直新建消息而不是追加,根因往往是 reply_id 没轮换或被意外重置——观察这里的值。

第二次:看 context 到底装了什么

断点 _agent.py:2293_call_model 拼 summary 处)。观察 self.state.context 的每条消息的 idrolename,以及 self.state.summary。如果你的对话历史不对,多半在这里就能看出来——比如本该一轮的消息被拆成了多条(reply_id 不一致),或 summary 没拼上。

第三次:看工具缓存有没有命中

断点 _state.py:57get_cache 的循环里)。观察 file_pathentry.updated_at 和 await aiofiles.os.path.getmtime(file_path) 的返回值。缓存总是 miss,多半是文件被频繁改动导致 mtime 总对不上,或 max_cache_files/max_cache_bytes 太小被 LRU 淘汰了。

第四次:看中间件状态

断点 _budget.py:181middle_context.get(...))。观察 agent.state.middle_context 的结构。如果你的中间件状态丢失,检查 middleware_key 是否稳定(别每次构造新字符串),以及 reply 结束有没有误删。

🔑 最关键的对象

self.state(AgentState 实例)。状态相关的 bug 99% 能从这个对象的字段值看出来——重点看 context 的消息结构、reply_id 的一致性、四个子 context 的内容。

七、如何扩展这个模块

✅ 应该改(推荐路径)

加自定义中间件状态:往 middle_context[middleware_key] 里塞,外层 key 用中间件类名或固定字符串。这是中间件存跨轮状态的标准做法,不用动 AgentState 的字段表。

切换工具组:改 tool_context.activated_groups。第 5 篇的 ResetTools 元工具就是这么做的——往这个列表加/删组名,_toolkit 下次过滤时就生效。

清缓存:调 tool_context.clean_file_cache(reserved_paths),只保留还在 context 里的文件缓存。_agent.py:2100 在压缩上下文时就是这么做的。

⚠️ 不应该改(有更优替代)

想加新的强类型状态桶:不要直接往 AgentState 堆字段。先问自己——这个状态是不是某个已有关注点?是就进对应子 model(比如工具相关进 ToolContext);是全新的、用户可扩展的关注点,就走 middle_context。只有"框架核心、所有 Agent 都有、需要强类型"的状态才值得加第四个强类型桶。

想给 ToolContext 加新方法ToolContext 已经自带缓存方法,逻辑自洽。要加新功能考虑清楚是不是工具相关的——别把它变成杂货铺。

🚫 千万不要改(动了会破坏不变量)

append_context 的尾消息复用判断_state.py:206-211):那四个条件(context 非空 + role=assistant + name 相同 + id==reply_id)是"同一轮 reply 的 block 拼进同一条消息"的保证。改坏任何一个,要么一轮 reply 被拆成多条消息(LLM 看到的对话结构错乱),要么不同轮的 block 被错误合并。

reply_id 的轮换时机_agent.py:732):必须在每轮 reply 开始时重新生成。如果在轮中间重置,append_context 会误判"要新建消息",把一轮 reply 拆碎;如果从不重置,所有轮次的 block 全堆进第一条消息。

has_awaiting_tool_calls 的状态判断_state.py:244-250):它基于 ToolCallState.ASKING 和 SUBMITTED + 无匹配 result 两个条件。放宽它(比如把 ASKING 也当成"已完成"),Agent 会在工具还在等用户确认时就结束 reply,挂起的工具调用永远等不到结果。

新增一个强类型状态桶(谨慎)

如果你确实需要框架级的新状态桶(比如 tracing 状态),步骤是:

  1. 定义新的 BaseModel 子类(仿照 ToolContext
  2. 在 AgentState 加一个字段(_state.py,带注释分桶)
  3. 确认它的序列化/反序列化符合预期(BaseModel 默认支持,但自定义类型要注意)
  4. 想清楚它要不要随 model_dump() 持久化——有些运行时状态(如锁、连接池)不该落盘,需要在序列化时排除

第 4 步最容易漏——AgentState 默认整体序列化,新加的字段会自动进存盘。如果新桶装的是不该持久化的东西(如临时连接、运行时锁),要用 Pydantic 的 Field(exclude=True) 或 model_config 排除。

八、本模块最值得学习的设计

1. 按关注点分桶的可组合状态

这是 state 模块给所有"需要多维运行时状态"的服务做的示范。AgentState 不是个上帝对象,而是几个职责单一的子 model 的组合:权限归 PermissionContext、工具归 ToolContext、任务归 TaskContext,中间件走逃生舱口。

好处是每个子 model 可以独立演进、独立测试。ToolContext 加缓存方法不影响权限逻辑;新增中间件不用碰任何强类型桶。而整体又是一个 BaseModel,对外是一个干净的序列化单元。

这套思路可以原样搬到:一个 Web 服务的请求上下文(auth/storage/cache/tracing 各一桶)、一个工作流引擎的执行上下文(variables/locks/history 各一桶)——任何"状态是多维的、又要整体持久化"的场景。

2. 强类型桶 + 逃生舱口的二元结构

前三桶是强类型(PermissionContext 等),第四桶 middle_context 是 dict[str, Any]。这不是设计不彻底,而是刻意的开放-封闭权衡

  • 强类型桶服务"框架知道需要什么"的场景——类型安全、IDE 补全、重构友好
  • 逃生舱口服务"框架不知道用户会需要什么"的场景——开放扩展、零修改

这种"核心强类型 + 边缘逃生舱口"的二元结构,比"全强类型"(扩展性差)和"全 dict"(类型不安全)都好。很多框架的状态对象要么僵化(加个字段要改框架),要么全靠 dict(写起来全靠记忆)。AgentScope 给了第三条路。

3. 双 ID 体系驱动流式写入

session_id 和 reply_id 的双 ID 设计,把"会话级"和"单轮级"两个时间维度编码进状态本身。append_context 靠 reply_id 做复用判断,实现"一轮 reply 的所有产出拼进同一条消息"——这个机制完全由数据(ID 是否相等)驱动,不需要额外的"当前在写哪条消息"的状态标志。

这个思路可以搬到任何流式/增量写入的场景:用 ID 区分"同一批次的追加"和"新批次的开始",比维护一个显式的 current_batch 指针更可靠(ID 是不可变的,指针是可变的)。

4. 状态查询封进状态对象本身

has_awaiting_tool_calls 这种"基于 context 的状态判断"被封装进 AgentState,而不是散落在 agent 主循环。依据是"信息专家"原则——判断的依据(context 里的 block 状态)完全在 state 内部,就该由 state 来回答。

这避免了 agent 主循环里堆满"查尾消息有没有 ASKING""查 tool_call 有没有匹配 result"这类碎片逻辑。主循环只问一个布尔问题,state 负责回答。状态对象不只是一个数据袋,还是一个会回答问题的对象。

一个值得注意的细节:middle_context 的序列化

middle_context: dict[str, Any] 会随 AgentState 整体序列化——这意味着中间件塞进去的状态也会落盘。这对大部分中间件是合理的(预算累计要跨轮保存),但如果某个中间件往里塞了不可序列化的东西(文件句柄、锁、连接),存盘时会炸。这是逃生舱口的代价:自由换来了责任。目前框架里用 middle_context 的中间件(_budget.py)塞的都是可序列化的数字/字典,没有踩这个坑。

九、阅读建议

源码阅读顺序:

  1. 先读 _state.py:149-192AgentState 字段定义)——扫一眼四桶结构,建立"状态分了哪几类"的整体认知。注意那些分隔注释,它们是分桶意图的明证。
  2. 再读 append_context:194-222——理解 reply_id 怎么驱动流式写入。这是 state 模块唯一有非平凡逻辑的方法。
  3. 然后读 has_awaiting_tool_calls:224-251——看状态查询怎么收口第 3 篇的状态机。注意它只看尾消息、只看特定 name。
  4. 读 ToolContext 的三个方法(:46-139——这是子 model 自带方法(不是纯数据)的范例,LRU 双上限淘汰值得看。
  5. 扫 _task.py——知道 Task 的 DAG 雏形(blocks/blocked_by)即可,逻辑简单。
  6. 最后看消费方(可选):_agent.py:732(reply_id 轮换)、_agent.py:2293(summary 拼接)、_budget.py:128-143(middle_context 用法)。这是把 state 放回框架里理解它的读写时机。

可以跳过的地方:ReadCacheEntry:23)是个纯数据类,4 个字段,扫一眼即可。TaskContext:142)只有一个字段,不用停。

十、阅读完成以后

读完这篇,你应该能回答:

  • 为什么需要这个模块:Agent 的运行时状态是多维的(对话/权限/工具/任务/中间件),需要一个组织方式让它们互不污染、又能整体持久化。state 模块用分桶组合 + Pydantic model 回答了这个问题。
  • 为什么这样设计:Pydantic model 换来天然序列化(决策 1)、按关注点分桶换来可扩展性(决策 2)、双 ID 体系换来流式写入(决策 3)、逃生舱口换来开放扩展(决策 4)、状态查询封装换来主循环简洁(决策 5)。
  • 代码怎么运行:每轮 reply 开始轮换 reply_id → _call_model 读 summary+context 喂 LLM → Agent 产出的 block 经 append_context 按 reply_id 拼进同一条消息 → 工具/中间件读写各自的子 context → reply 结束后上层存储层把整个 state 落库。
  • 如果要改,改哪里:加中间件状态用 middle_context;切工具组改 activated_groups;清缓存调 clean_file_cache。不要碰 append_context 的复用判断、reply_id 的轮换时机、has_awaiting_tool_calls 的状态判断。

真正应该带走的:状态对象不该是上帝对象,而该是几个职责单一的小 model 的组合;核心关注点用强类型,边缘扩展用逃生舱口——这套「按关注点分桶的可组合状态」,是 state 模块给所有需要多维运行时状态的服务做的示范。


你写 Agent 的时候,运行时状态是怎么存的?是塞进一个大 dict,还是拆成了几个小对象?有没有踩过"状态越加越多、最后谁都不敢动"的坑?

想深入的同学再想一个:如果你要给 Agent 加一个"tracing 状态"桶(记录每次 LLM 调用的耗时、token 数),你会做成强类型子 model,还是塞进 middle_context?两种选择各自的代价是什么?

追更:系列持续更新,关注不错过。

裂变:转发到 Agent / LLM 应用开发群或朋友圈,帮同样在读 AgentScope 源码的朋友省时间。

     AgentScope 2.0 源码解析系列 · state 模块   
基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-09 19:48:32 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/917838.html
  2. 运行时间 : 0.175460s [ 吞吐率:5.70req/s ] 内存消耗:4,756.58kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=117a4fb14bc1fe30e247066109322d9a
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 4.22 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.11 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000919s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000967s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000325s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000262s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000455s ]
  6. SELECT * FROM `set` [ RunTime:0.000180s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000568s ]
  8. SELECT * FROM `article` WHERE `id` = 917838 LIMIT 1 [ RunTime:0.001292s ]
  9. UPDATE `article` SET `lasttime` = 1786276112 WHERE `id` = 917838 [ RunTime:0.001711s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000213s ]
  11. SELECT * FROM `article` WHERE `id` < 917838 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000359s ]
  12. SELECT * FROM `article` WHERE `id` > 917838 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000867s ]
  13. SELECT * FROM `article` WHERE `id` < 917838 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000911s ]
  14. SELECT * FROM `article` WHERE `id` < 917838 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000733s ]
  15. SELECT * FROM `article` WHERE `id` < 917838 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.000661s ]
0.177263s