希望能够在这篇文章中综合讨论搭建代理的各种方案,这里的代理定义并不局限于能力边界要求,而是从普通的工作流一直到大规模skill/框架搭建出的Agent。一个合理的Agent不应该盲目的增加复杂度,而应该根据任务的场景、ROI等等因素来衡量方案

工作流

对于工作流,市面上有很多成熟的框架例如n8n coze Dify等等,目前我只用过n8n,所做的工作也比较简单。主要原因是我的场景中并没有太多完全流程化的东西,而工作流的方案往往更适合与这些。

它的基本步骤就是首先要提出一个SOP的流程,然后选一个基模,我认为这里尤其应该注意的是,Deepseek是没有多模态能力的,而是通过OCR来实现图片识别,在搭建工作流时应该权衡经济和多模态能力,是否选择Deepseek。而搭建一个工作流的麻烦点就是各个节点的配置,举一个基础的例子就是搭建邮件工作流,如果是谷歌邮箱,其中的Oauth流程还是挺复杂的。有一个SOP流程+节点配置好,实际上一个工作流就不难搭建了。而对于不同的任务,需要在以上提到的框架中多找找,可能某个框架针对这个任务场景设计的节点要更加优雅和安全。

同时大多数工作流工具主要是DAG,虽然支持Loop,但是远远没有代码灵活。

Agent 框架

当然了框架肯定不仅限于LAngchain团队开发的,还有CrewAI\ 另外还关注到了技术群里一个人开发的生态瓶框架等等。只是LangChain的接口更加通用, 即使并不好用..

相较于Raw SDK, LangChain框架统一了接口,提供了持久化存储,包括checkpoint和threadID接口,能够实现历史回溯。但是Langchain的封装太高。例如使用creatAgent一个接口完成了整个agent创建,因此这里解释更加自由的Langgraph。SDK 中的 while 循环虽然简单,但难以持久化。LangGraph 将流程定义为 Nodes(节点/动作) 和 Edges(边/逻辑跳转)。具体可以看一下文档,Langchaint团队还开发了很多Agent的接口特性。使用Langchain快速搭建一个agent 完成一些基本验证是没问题的,但是欠下来的技术债也是很可观的. 所以接下来首先说一下其弊端.

过度封装

如果你尝试去 Debug 一个稍微复杂的 LangChain 链路,你会陷入无穷无尽的底层源码跳转。一个简单的提示词拼接和模型调用,中间可能穿插了 BasePromptTemplate -> StringPromptTemplate -> PromptTemplate 等五六层继承。

ConversationalRetrievalChain 这种高级链,一行代码就把历史记录、向量检索、Prompt 拼接、大模型调用全部打包了。当模型输出不符合预期时,你根本不知道是检索阶段出了问题,还是内部隐式拼接的 Prompt 破坏了上下文,或者是内置的 Output Parser 解析失败。你想改动其中一个微小的逻辑,就必须去继承并重写它极其复杂的底层类。

为什么 LangChain 要把 LCEL、提示词、追踪做得到处都是抽象,让你无法轻易看清数据流?(道听途说)可能是 LangSmith。因为 LangChain 底层复杂的封装和 LCEL 带来的 Debug 灾难,开发者在本地几乎无法调试。这时候,官方就会推荐你接入 LangSmith。

LangeGraph

Langchain团队目前已经有了很多产品,包括 langchain langgraph,langsmth langserver等,不具体介绍,但是langchain是一个DAG, 甚至不能够完成一个agentloop, 所以从构建agent角度而言,重点谈langgraph

下面是一段使用langGraph改造的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import json
import operator
from typing import Annotated, TypedDict, Union, List

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
from langgraph.graph import StateGraph, END, START
from langgraph.prebuilt import ToolNode
from langgraph.checkpoint.memory import MemorySaver


@tool
def start_stream(stream_id: str, protocol: str):
"""Start a stream with given stream_id and protocol (rtmp/hls only)."""
if protocol not in ['rtmp', 'hls']:
return json.dumps({"error": "Unsupported protocol"})
return json.dumps({"stream_id": stream_id, "protocol": protocol, "status": "starting"})

@tool
def stop_stream(stream_id: str):
"""Stop a running stream."""
return json.dumps({"msg": f"stream_{stream_id} has been stopped."})

@tool
def get_stream_status(stream_id: str):
"""Get technical status (bitrate, fps) of a stream."""
if stream_id == "news_channel":
return json.dumps({"bitrate": 500, "status": "unstable", "fps": 10})
elif stream_id == "sports_channel":
return json.dumps({"bitrate": 4000, "status": "live", "fps": 60})
else:
return json.dumps({"error": "offline"})

tools = [start_stream, stop_stream, get_stream_status]


# Annotated[list, operator.add]:新的消息会 "Append" 到列表,而不是覆盖
class AgentState(TypedDict):
messages: Annotated[List[BaseMessage], operator.add]


llm = ChatOpenAI(
model="gpt-4o-mini",
api_key=,
base_url=
)
llm_with_tools = llm.bind_tools(tools)

def agent_node(state: AgentState):
"""思考节点:调用 LLM 决定下一步"""
messages = state["messages"]
response = llm_with_tools.invoke(messages)
# 返回新的消息(LangGraph 会自动将其 append 到 state)
return {"messages": [response]}


tool_node = ToolNode(tools)

# 定义图
workflow = StateGraph(AgentState)

# 添加节点
workflow.add_node("agent", agent_node)
workflow.add_node("tools", tool_node)

# 定义边 (Edges)
workflow.add_edge(START, "agent") # 入口 -> 思考

# 定义条件边 (Conditional Edges)
# 逻辑:如果 agent 输出包含 tool_calls,跳转到 "tools" 节点,否则结束
def should_continue(state: AgentState):
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tools"
return END

workflow.add_conditional_edges(
"agent",
should_continue,
["tools", END]
)

# 工具执行完后,必须跳回 agent 继续思考
workflow.add_edge("tools", "agent")

# memory=checkpointer 开启了持久化记忆
checkpointer = MemorySaver()
app = workflow.compile(checkpointer=checkpointer)

# 模拟运行
if __name__ == "__main__":
# 配置线程 ID,模拟不同用户的会话
config = {"configurable": {"thread_id": "user_session_001"}}

print("--- 任务:复杂流控推理 ---")
user_input = "检查一下 news_channel 的状态,如果它不稳定或者码率低于 1000,就把它重启一下。"

# 这一步包含了整个 While 循环 + 状态管理
# Stream 模式允许我们看到中间步骤
for event in app.stream(
{"messages": [HumanMessage(content=user_input)]},
config=config
):
for key, value in event.items():
print(f"\n[Node: {key}]")
# 打印最新的消息内容
last_msg = value["messages"][-1]
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
print(f" -> 计划调用: {last_msg.tool_calls[0]['name']}")
elif hasattr(last_msg, "content"):
print(f" -> 内容: {last_msg.content}")

print("\n\n====== 最终结果 ======")
# 由于有 checkpointer,我们可以随时获取当前状态
final_state = app.get_state(config)
print(final_state.values["messages"][-1].content)

Langgraph提供的其他API

human in the loop

通过 interrupt_before 或 interrupt_after 来暂停图的执行,等待人工确认或修改状态。实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# ... (前面的代码保持不变) ...

# 1. 修改编译步骤:增加 interrupt_before
# 这意味着在进入 "tools" 节点前,程序会暂停
app = workflow.compile(
checkpointer=checkpointer,
interrupt_before=["tools"] # <--- 关键修改
)

if __name__ == "__main__":
config = {"configurable": {"thread_id": "user_session_hitl_01"}}

print("--- 阶段1:触发任务 ---")
user_input = "news_channel 好像卡了,帮我重启一下。"

# 第一次运行:Agent 会思考并决定调用工具,但会在执行前暂停
for event in app.stream({"messages": [HumanMessage(content=user_input)]}, config=config):
for key, value in event.items():
print(f"[Node: {key}] 正在处理...")

# 此时,程序暂停了。我们可以检查 Agent 到底想干什么
snapshot = app.get_state(config)
next_step = snapshot.next

if "tools" in next_step:
last_msg = snapshot.values["messages"][-1]
tool_call = last_msg.tool_calls[0]
print(f"\n[系统暂停] Agent 申请执行: {tool_call['name']} 参数: {tool_call['args']}")

# 模拟人工审批
approval = input(">>> 是否批准执行?(y/n): ")

if approval.lower() == 'y':
print("--- 阶段2:批准并继续 ---")
# 传入 None 表示继续执行当前状态,不添加新消息
for event in app.stream(None, config=config):
for key, value in event.items():
print(f"[Node: {key}] {value}")
else:
print("--- 阶段2:拒绝并告知 Agent ---")
# 我们可以直接注入一条 ToolMessage 表示拒绝,或者注入一条 HumanMessage 告诉它不行
app.update_state(
config,
{"messages": [HumanMessage(content="管理员拒绝了重启操作,请检查是否有其他非破坏性方案。")]}
)
# 继续运行,Agent 会收到拒绝信息并重新思考
for event in app.stream(None, config=config):
print(f"[Node: {key}] {value}")

checkpoint与threadID

在Langgraph中通过快照策略将每个操作保存为一个checkpoint,将每一个会话保存为一个threadID,可以通过数据库以及序列化保存这个快照,并设计逻辑实现查找历史会话或者回溯历史checkpoint。而当返回某个checkpoint进行新的操作时,会被分支到一个新的checkpoints,而不是直接覆盖。关于threadID和checkpoint的过滤、排序等策略,都可以在数据库中实现。

subagent

在 LangGraph 中实现层级 Agent 的思路,可以定义另一个 StateGraph (比如 stream_monitor_graph),编译成 monitor_app,然后把它作为一个 Node 放入你的主图 workflow 中。

上下文压缩

虽然 operator.add 导致了状态的无限增长,但 LangGraph 并非束手无策。对于简单的场景,我们在 Node 内部使用 trim_messages 进行基于 Token 的动态截断;而对于需要长期记忆的场景,我们可以设计一个专门的 SummarizeNode,利用 LangGraph 特有的 RemoveMessage 接口,定期对 State 进行上下文压缩。
一个简单的sumarizeNode设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from langchain_core.messages import RemoveMessage, SystemMessage, HumanMessage

def summarize_node(state: AgentState):
summary_prompt = "请总结以上对话的关键信息..."
messages = state["messages"]

# 调用 LLM 生成总结
summary = llm.invoke(messages + [HumanMessage(content=summary_prompt)])

# 找出除了最新的 2 条消息以外的所有旧消息 ID
# 我们保留最后 2 条是为了上下文连贯性
messages_to_delete = [m.id for m in messages[:-2]]

# 构造删除操作
delete_operations = [RemoveMessage(id=mid) for mid in messages_to_delete]

# 将总结作为新的 SystemMessage 插入(或者作为一段上下文)
new_summary_msg = SystemMessage(content=f"Background Summary: {summary.content}")

# 返回:删除旧的 + 插入新的总结
return {"messages": [new_summary_msg] + delete_operations}

# 需要在 workflow 中添加条件边
def should_summarize(state: AgentState):
if len(state["messages"]) > 10:
return "summarize"
return END # 或者继续其他逻辑

或者使用官方的trim工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from langchain_core.messages import trim_messages

# 定义裁剪器
trimmer = trim_messages(
max_tokens=2000,
strategy="last",
token_counter=llm, # 使用模型的 tokenizer 计数
include_system=True, # 总是保留 System Prompt
allow_partial=False,
start_on="human" # 确保截断后的第一条是人类消息(避免从中间的 tool 切断)
)

def agent_node(state: AgentState):
# 在调用 LLM 前先过一遍 trimmer
trimmed_messages = trimmer.invoke(state["messages"])
response = llm_with_tools.invoke(trimmed_messages)
return {"messages": [response]}

SDK实现

我认为目前agent的设计形态还没有收敛,很多工程细节处理上都没有一个共识,所以与其让agent框架束手束脚,不如自己从头造轮子, 更何况我认为很多轮子是没比框架复杂多的. 对于一个agent而言,最核心的就是如何进行循环, 参考langgraph,是使用一个State来管理的,通过将message不断的append来管理上下文.

进入循环之前还要引入预处理,模型的上下文是有限制的, 用户的经费是有限制的, 注意力并不是全局平均的,因此如何对放入循环的全部信息进行处理就是一门学问,毫无疑问需要的是裁剪->压缩. 在循环之中要进行重试机制设计,以及简单的错误处理,因为即使模型经过了sft,但是我认为也不能够保证格式的百分百正确,做一些基础冗余是必要的,另外还要实现消息的流式传递,改善用户体验. 单次的query结束之后应该进行进一步判定,根据sdk提供的字段信息,是需要调用工具,还是返回了超出窗口等基本错误,或者单纯的结束. 依据此来选择continue路径. 完成queryloop之后仍然应该继续处理, 可以设立一些钩子,对本次会话或者其他信息总结,可以总结到项目级别的spec或者全局级别的Memory.md中.

除了循环系统,工具系统设计也是至关重要, 对tool设立一个标准的schema\权限级别\统一的调用接口\facade调用层, 都是必不可少的.除了这些基本接口还应该通过结构字来设立并行的执行能力, 但是要有所权衡.在工具调用的每个batch中判断并执行.

上下文的构建也很关键, 我认为由于transformer的特点,第一条消息应该注入全局AGENTS.md 以及项目级别的AGENTS.md. 另外还应该选择注入当前环境的环境变量等信息,来帮助模型完善上下文. 在单次loop中的不同query中也应该使用state来追加消息,避免丢失上下文,即使api侧可能提供缓存,但是agent设计侧不应该轻信这一点,即使token的耗费较大.通过不断的追加以及循环中的裁剪->压缩来完成一个可控的多轮任务执行.

除了以上基本信息,LSP引入\MCP引入\搜索机制设计\多agent机制\skill加载 也是不可或缺的. LSP client能够帮助agent 获得更加丰富的代码上下文, MCP client可以获得更加丰富的能力,多agent可以更优雅的实现分离的互不干扰的上下文.skill通过分层设计skill.md实现了一种渐进式加载的策略,为agent提供一个标准的SOP工作流程, 关于skill设计可以看官方博客

这个部分会一直写下去,当我在agent设计方面有一些新的思路的时候..

skill.md设计

linuxdo上有一篇文章写的很好,以下是一个案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---
# === 元数据区 (给系统/Router看) ===
name: stream_diagnosis_expert
version: 1.0.2
author: ops_team
description: >-
专门用于诊断直播流卡顿、音画不同步、黑屏等质量问题。
当用户反馈观看体验不佳、报错或请求技术排查时,激活此技能。

# === 配置区 (给执行引擎看) ===
model_config:
temperature: 0.1 # 诊断类任务需要低创造性
max_tokens: 2000

# === 工具绑定区 (给 ToolBinder 看) ===
# 这里填写 Python 函数的字符串名称,加载时映射到真实函数,
tools:
- get_stream_probe_data # ffprobe 封装 see [get_stream_probe_data](get_stream_probe_data.md)
- check_cdn_health # CDN 状态查询 see [check_cdn_health](check_cdn_health.md)
- get_transcode_logs # 转码日志获取 see [get_transcode_logs](get_transcode_logs.md)
- restart_stream # 重启流 (高危操作,需谨慎) see [restart_stream](restart_stream.md)

# === 依赖/前置条件 (可选) ===
requires_context:
- stream_id

沙箱机制

对于沙盒机制, 其技术实现方案较为复杂, 大致路线分为云侧和本地,

云侧

在云侧, ai需要一个快速启动,快速删除的沙箱,因此冷启动速度至关重要,主要的技术方案有基于虚拟机\ docker \ wasm的,另外这篇文章还谈论了关于unikernel在沙箱实现上的可能性. 云侧虚拟机技术各类机制的原理\方案这篇文章,也有描述. 由于我没有做过云侧的沙盒,这里仅仅列下和ai探讨的恶性能比较

评估维度 系统进程级限制 (Seccomp) 传统容器 (Docker) 运行时沙箱 (gVisor / WASM) 微型虚拟机 (Firecracker)
隔离边界 进程级、系统调用级限制 操作系统级、共享内核隔离 用户态虚拟内核/指令级隔离 硬件级、独立内核强隔离
启动延迟 $< 1\text{ ms}$ $100\text{ ms} - 500\text{ ms}$ WASM ($<1\text{ ms}$) / gVisor ($200\text{ ms}$) $100\text{ ms} - 200\text{ ms}$
宿主性能损耗 $\approx 0%$ $\approx 1% - 3%$ WASM (低) / gVisor ($10% - 30%$) $\approx 3% - 5%$
多语言生态兼容 极差(难以配置普适策略) 极佳(利用 Dockerfile 生态) WASM (差) / gVisor (极佳) 极佳(完整的操作系统生态)
防逃逸能力 低(无法抵御内核提权) 中(存在已知漏洞逃逸风险) 高(切断物理内核调用) 最高(硬件级锁死安全边界)

本地

而在本地, 由于agent客户端应该充分接触项目,并访问本地文件,完成搜索, 权限管理等操作, 这些操作灵活而且难以归类, 同时要求依赖较少,因此使用系统的运行时限制就变得更为合适, 而进程级别的限制复杂处就在于系统的适配,对于linux, 可以使用bubblewrap 对于win,需要使用win32的API. 我在本地沙盒工具的实现是绑定到了bash工具上,通过参数的直接叠加完成系统的适配, 但是bash的网络权限配置是一个难以tradeoff的点,观察claudecode 和codex的实现,为了减少适配难度和依赖,他们都没有对bash的网络权限做限制, 因此我也暂时没有实现这一点,但是实际上这是可以被解决的,具体方案如下

  1. 接口层:强制声明网络意图 (Declarative Schema)
    修改大语言模型(LLM)调用的 execute_bash 工具定义。取消 LLM 的隐式网络特权,强迫其在执行命令前,以结构化方式准确声明网络访问的边界。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"name": "execute_bash",
"description": "在沙箱内执行 bash 命令。",
"parameters": {
"type": "object",
"properties": {
"command": { "type": "string" },
"required_domains": {
"type": "array",
"items": { "type": "string" },
"description": "本次执行必须访问的外部域名列表(如 github.com, pypi.org)。如果不需联网,请留空。"
},
"network_justification": {
"type": "string",
"description": "解释为什么需要访问上述域名。"
}
},
"required": ["command", "required_domains"]
}
}
  1. 网络层:沙箱内流量强制劫持 (Traffic Hijacking)
    绝不能让 bwrap 沙箱直接共享宿主机的网络命名空间。

隔离配置:启动沙箱时,关闭默认路由(Linux 下为 bwrap –unshare-net,结合 slirp4netns 提供隔离的用户态网络栈)。

代理注入:在 Go 宿主代理中实现一个极轻量的 HTTP/HTTPS 正向代理服务器(监听在 127.0.0.1 上的随机高端口)。在启动沙箱子进程时,注入环境变量 HTTP_PROXY 和 HTTPS_PROXY,强行将沙箱内所有工具(curl, git, go, pip)的出站流量导向 Go 宿主代理。

  1. 控制层:分级鉴权策略 (Tiered Authorization)
    当沙箱内的命令发起网络请求时,流量会抵达 Go 宿主代理。Go 代理拦截 TLS SNI(服务器名称指示)或 HTTP Host 头,并依据以下分级策略执行判定:

拦截规则 A(绝对阻断 SSRF):
硬编码丢弃所有指向本地环回地址(127.0.0.0/8)和局域网私有地址段(192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12)的请求。这从物理上杜绝了 Agent 恶意扫描宿主机本地服务或企业内网的可能性。

拦截规则 B(静态基建白名单放行):
维护一个软件工程基础设施白名单库(如 github.com, proxy.golang.org, registry.npmjs.org, pypi.org, archive.ubuntu.com)。如果请求的域名命中该库,静默放行,保证标准包管理工具的顺畅运行。

拦截规则 C(非标准域名的人机交互审计 Human-in-the-loop):
如果 LLM 要求访问非标准域名(例如通过 wget 下载某个学术网站的数据集),Go 宿主代理挂起该网络请求,并在终端(或 UI)向当前登录用户抛出提示:
“Agent 正在尝试访问 unknown-domain.com(理由:下载测试数据集)。是否允许?(Y/N)”
用户确认后,该域名被加入当前会话的动态白名单并放行;若拒绝,向沙箱返回 TCP 连接重置(RST),导致 bash 命令报错中断。

而对于win的适配,在 Windows 环境下,Spec 的映射需要组合多个 Win32 内核对象。通过串联 强制完整性控制 (MIC)访问令牌 (Access Token)作业对象 (Job Object) 三个内核机制,构建一个受限执行流。

整体实现分为四个时序阶段:

  1. 环境准备阶段:建立合法写入通道, Windows 默认的文件系统完整性级别为 Medium(中等)。沙盒进程将被降级为 Low(低),根据“不向上写(No Write Up)”的安全策略,沙盒将失去对整块硬盘的写入权限。
  • 动作:在 Agent 发起执行任务前,宿主程序必须定位或创建本次任务专属的 Workspace 目录。
  • 实施:通过外部系统命令(icacls <路径> /setintegritylevel (OI)(CI)L)或底层的 ACL API,显式将该目录的完整性级别降低为 Low IL,并设置子目录和文件的继承标志。这是沙盒进程唯一合法的物理输出出口。
  1. 上下文构建阶段:颁发受限凭证, 宿主代理(Go 程序)需要为其即将启动的子进程伪造一张权限被大幅削减的“身份证”。
  • 动作 1(剥离特权):调用 OpenProcessToken 获取宿主当前的中等完整性令牌。调用 CreateRestrictedToken,传入 DISABLE_MAX_PRIVILEGE,硬性剥离诸如关机、系统环境修改、调试等所有管理员特权。
  • 动作 2(强制降级):构造一个标识为 SECURITY_MANDATORY_LOW_RID 的安全标识符(SID)。调用 SetTokenInformation,将受限令牌的完整性级别(Integrity Level)强制覆盖为 Low IL。
  1. 边界确立阶段:生命周期强制绑定

大模型生成的动态脚本(如长期挂起的 Web Server 或陷入死循环的编译任务)极易产生难以回收的孤儿进程。

  • 动作:调用 CreateJobObject 创建一个作业对象。
  • 实施:调用 SetInformationJobObject,为该作业对象注入 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 限制策略。此策略向内核声明:一旦持有该作业对象句柄的宿主进程(Go 代理)退出,内核必须立即无条件销毁该作业对象内包含的所有进程树。
  1. 进程注入与执行阶段:安全点火与通信

必须消除“进程启动”与“权限限制生效”之间的时间差(Time-of-Check to Time-of-Use 竞争条件)。

  • 动作 1(挂起启动):使用步骤 2 生成的受限令牌,调用 CreateProcessAsUser 启动目标命令(如 pwsh.exe -Command ...)。必须设置 CREATE_SUSPENDED 标志位。此时进程已在内存中创建,但主线程被内核冻结,尚未执行任何用户态代码。
  • 动作 2(装载隔离罩):调用 AssignProcessToJobObject,将处于冻结状态的子进程句柄塞入步骤 3 创建的作业对象中。
  • 动作 3(唤醒与 I/O 接管):将子进程的标准输出(Stdout)和标准错误(Stderr)重定向至 Go 宿主预先创建的匿名管道。最后调用 ResumeThread 唤醒主线程,开始实际执行大模型下发的指令。

本站由 Edison.Chen 创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。