在搭建智能体(Agent)应用时,最让人头疼的往往不是大模型本身,而是 Agent 的“骨架”怎么搭:工具列表写死、任务流程写死、上下文越堆越长……一旦业务需求变化,静态配置的 Agent 就要改代码、发版本,链路还特别容易断。JIT-Agent 正是为了解决这类问题出现的一种动态生成智能体框架的实现思路。
这里所说的“模型”,并不是指某一个预训练大模型,而是一种框架层面的设计模型:JIT(Just-In-Time,即时生成)。它借鉴了 JIT 编译“按需生成、运行时执行”的思想,把 Agent 中的工具选择、提示词组装、执行计划从启动阶段推迟到每一次任务请求发生时动态完成。本文会从核心概念讲起,再手把手实现一个轻量 JIT-Agent 框架,最后给出常见问题和工程建议。
1. 背景与核心概念
1.1 什么是 JIT-Agent
JIT-Agent 可以理解为一种“动态生成智能体框架”的设计模型。通常一个 Agent 由三部分组成:大模型(LLM)、工具集(Tools)、执行策略(Execution Strategy)。传统做法是在系统启动时把全部工具和固定提示词注册进去,之后所有请求都走同一套逻辑。这种模式实现简单,但灵活性不足。
JIT-Agent 改变了生成时机:当用户请求到达时,框架先分析当前任务,确定“这个任务需要用到哪些工具”,再去注册表中动态加载对应的工具元数据和执行函数,同时生成针对性的系统提示词,最后执行任务并返回结果。整个过程像 JIT 编译一样,不是提前把一切准备好,而是“用到什么才生成什么”。
这样做的好处很明显:
- 减少上下文中的工具描述,节省 Token;
- 模型不容易被无关工具干扰;
- 新增工具时无需修改主流程,只要在注册表中登记即可;
- 可以根据不同用户、不同场景生成不同形态的 Agent。
1.2 JIT-Agent 解决什么问题
静态 Agent 在实际项目中会遇到几个突出问题。
第一,工具全量注册导致上下文膨胀。假设一个 Agent 注册了 30 个工具,每次调用大模型时都要把 30 个工具的描述传给模型,即使本次任务只需要其中 2 个。随着工具数量增长,Token 开销会越来越大,模型出错的概率也会上升。
第二,任务流程固定,难以应对开放性问题。很多 Agent 是“定义好 Function、定义好 Prompt、执行固定流程”,对简单任务勉强可用,但遇到无法预料的用户输入,就会显得僵化。
第三,扩展成本高。每次新增工具,都要修改 Agent 的初始化代码,甚至要调整提示词模板,维护成本非常高。
JIT-Agent 的核心思路是“以任务为中心”:先把所有工具注册到一个中心注册表,每次请求到来时,由规划器决定加载哪些工具,再动态生成执行计划。这样一来,Agent 的结构与具体业务解耦,新增工具只需要写一个函数并注册,无需改动主框架。
当然,动态生成也不是没有代价。运行时分析任务、动态加载工具都会带来额外延迟,而且规划结果可能不稳定。所以 JIT-Agent 更适合任务类型丰富、工具数量较多、需要快速扩展的场景。
1.3 典型应用场景
JIT-Agent 的适用面很广,常见的有下面几类。
- 智能客服系统:用户问“我的订单什么时候到”,后台动态加载订单查询工具;用户问“退款流程是什么”,后台加载退款相关文档工具。
- 数据分析平台:用户输入“帮我画一个 7 月销售额折线图”,Agent 动态加载数据查询工具、图表生成工具,不需要把所有 BI 能力全部暴露给模型。
- 自动化运维助手:根据告警关键字动态选择日志查询、服务重启、健康检查等工具,避免把所有危险操作都暴露在同一个上下文中。
- 个人助理:根据用户当前需求动态决定调用天气、日历、导航还是音乐播放工具。
这些场景有一个共同特征:工具数量多、任务类型分散、单次请求实际用到的工具很少。如果全部静态加载,效率和安全都不理想。
2. 环境准备与版本说明
2.1 运行环境
本文的示例代码使用 Python 编写,核心部分只依赖 Python 标准库,不需要安装额外第三方包,方便读者直接复制运行。
- 操作系统:Windows / macOS / Linux 均可;
- Python 版本:建议 3.9 及以上;
- IDE:VS Code、PyCharm 均可,能运行 Python 脚本即可;
- 可选依赖:如果后续要接入真实大模型,需要根据你使用的模型服务安装对应 SDK,版本以官方文档为准。
示例项目结构如下:
jit_agent/ ├── agent/ │ ├── __init__.py │ ├── registry.py │ ├── planner.py │ ├── executor.py │ └── runtime.py ├── tools/ │ ├── __init__.py │ ├── weather.py │ └── calculator.py └── main.pyagent包负责框架核心逻辑,tools包存放具体业务工具,main.py是入口。这样分层的好处是:框架与业务工具分离,新增工具不需要改动框架代码。
2.2 版本说明
关于 Python 版本,建议使用 3.9 以上,因为代码中用到了类型注解等特性,更低版本可能需要调整。如果你要在生产环境中接入真实大模型,版本需要根据你实际使用的模型服务调整,本文示例重点演示框架实现思路。
3. JIT 动态生成的核心原理拆解
3.1 从“静态 Agent”到“动态生成”
先看一个静态 Agent 的典型实现。
# 静态注册,启动时写死 def get_weather(city: str): return f"{city} 天气晴" def calculate(expression: str): return eval(expression) TOOLS = { "get_weather": get_weather, "calculate": calculate, }这种方式在工具数量少时没有问题,但如果工具越来越多,问题就来了:
- 每个请求都要把所有工具的描述拼进 Prompt;
- 工具间如果有依赖关系,静态加载容易遗漏或冲突;
- 新增工具需要修改
TOOLS字典和 Prompt 模板。
JIT-Agent 的做法是引入“注册表 + 规划器”两层结构。注册表保存所有工具的元数据和执行函数,规划器在运行时根据用户输入决定加载哪些工具。核心思想是让“工具选择”变成动态过程。
3.2 工具注册表:动态生成的基础
注册表是 JIT-Agent 的地基。它维护一个全局字典,每个工具条目至少包含四个信息:工具名称、工具描述、参数 Schema、执行函数。名称用于唯一标识,描述用于给规划器做选择,参数 Schema 用于校验和生成调用参数,执行函数是真正运行的业务逻辑。
Python 中可以用装饰器实现一个极简注册表。
# agent/registry.py TOOL_REGISTRY = {} def register_tool(name, description, parameters_schema=None): def decorator(func): TOOL_REGISTRY[name] = { "name": name, "description": description, "parameters": parameters_schema or {}, "func": func, } return func return decorator def get_tool(name): return TOOL_REGISTRY.get(name) def list_tools(): return [ { "name": tool["name"], "description": tool["description"], "parameters": tool["parameters"], } for tool in TOOL_REGISTRY.values() ]这里的关键点是:list_tools只返回元数据,不返回执行函数。这样在把工具列表发给大模型时,不会暴露内部实现。执行函数只在真正执行时才通过get_tool获取,这是动态生成的关键一步。
3.3 任务规划:动态选择工具的入口
注册表准备好了,剩下的问题是:怎么知道当前任务需要哪些工具?这就是“规划器”的职责。
规划器的输入是用户请求文本,输出是一个或多个“工具调用计划”,每个计划包含工具名和参数。最简单的方式是关键词匹配,适合演示;更通用的方式是调用大模型的 function calling 能力,让模型自己从工具列表中选择合适的工具。
不管是哪种方式,规划器的核心逻辑都一致:先用当前注册表中的工具元数据生成候选列表,再根据用户输入选择工具并提取参数。这就是“动态生成”的核心体现:工具列表不是固定的,而是随着请求变化。
4. 完整实战:手写一个轻量 JIT-Agent 框架
4.1 定义业务工具
先创建tools/weather.py,定义一个查询天气的工具。
# tools/weather.py from agent.registry import register_tool @register_tool( name="get_weather", description="查询指定城市的天气情况", parameters_schema={ "type": "object", "properties": { "city": {"type": "string", "description": "城市名称,例如:北京"} }, "required": ["city"], }, ) def get_weather(city: str) -> str: # 真实项目中可替换为天气 API 请求 return f"{city} 今天晴,气温 25 摄氏度,空气质量良。"再创建tools/calculator.py,定义一个计算器工具。
# tools/calculator.py from agent.registry import register_tool @register_tool( name="calculate", description="计算简单的四则运算表达式", parameters_schema={ "type": "object", "properties": { "expression": {"type": "string", "description": "数学表达式,例如 3+5"} }, "required": ["expression"], }, ) def calculate(expression: str) -> str: # 演示代码直接使用 eval,生产环境请使用安全解析库 result = eval(expression) return f"{expression} = {result}"到这里,工具定义已经完成。注意两个文件都通过@register_tool装饰器把函数注册到全局注册表,但主流程代码并没有直接引用它们。这意味着将来新增工具时,只需要新增一个文件并导入一次即可。
4.2 实现工具注册表
之前已经给出registry.py的代码,这里再补充一个细节:工具注册顺序会影响规划器看到的候选列表。实际项目中建议在入口文件统一导入所有工具模块,保证注册完成后再启动 Agent。
# agent/__init__.py这个文件保持为空,用于标识agent是一个 Python 包。
4.3 实现任务规划器
接下来实现agent/planner.py。为了做到“零依赖即可运行”,演示版本用关键词匹配生成计划。
# agent/planner.py class Planner: def __init__(self, tools): self.tools = tools def plan(self, user_input: str) -> list: """ 根据用户输入生成执行计划。 演示版本使用关键词匹配,生产环境可替换为 LLM function calling。 """ plans = [] if "天气" in user_input: city = "北京" for candidate in ["北京", "上海", "广州", "深圳"]: if candidate in user_input: city = candidate break plans.append({"tool": "get_weather", "arguments": {"city": city}}) if "计算" in user_input or "算一下" in user_input: expression = user_input.replace("计算", "").replace("算一下", "").strip() plans.append({"tool": "calculate", "arguments": {"expression": expression}}) return plansPlanner接收tools参数,但在关键词匹配版本中没有直接使用。保留这个参数是为了后续替换成 LLM 规划器时,可以基于工具列表生成 function calling 请求。
如果用户输入同时包含“天气”和“计算”,比如“先看看北京天气,再计算 3+5”,规划器会生成两个计划,按顺序执行,这已经具备多步任务的雏形。
4.4 实现执行引擎
执行引擎负责真正调用工具函数。它从计划中取出工具名和参数,从注册表中获取执行函数,然后执行并返回结果。
# agent/executor.py from agent.registry import get_tool class Executor: def execute(self, plan: dict) -> str: tool_name = plan["tool"] tool = get_tool(tool_name) if not tool: return f"错误:工具 {tool_name} 未注册" try: result = tool["func"](**plan.get("arguments", {})) return result except Exception as e: return f"工具执行失败:{e}"这里做了两层保护:第一层判断工具是否存在,第二层捕获执行异常。实际项目中还应该增加参数校验,确保调用参数符合工具的parameters_schema,避免恶意输入或异常参数传入工具函数。
4.5 实现运行时装配
运行时是整个 JIT-Agent 的入口,它把注册表、规划器、执行器组装在一起,对外提供run方法。
# agent/runtime.py from agent.registry import list_tools from agent.planner import Planner from agent.executor import Executor class JITAgent: def __init__(self): self.tools = list_tools() self.planner = Planner(self.tools) self.executor = Executor() def run(self, user_input: str): print(f"用户输入:{user_input}") print("当前可用工具:") for tool in self.tools: print(f" - {tool['name']}: {tool['description']}") plans = self.planner.plan(user_input) if not plans: return "没有找到合适的工具来处理该请求" outputs = [] for plan in plans: print(f"动态生成计划:{plan['tool']} {plan.get('arguments', {})}") result = self.executor.execute(plan) outputs.append(result) return "\n".join(outputs)这个类体现了一个核心思想:JITAgent在初始化时只获取工具元数据,不直接绑定任何具体业务函数。每次运行请求时,规划器和执行器动态配合完成“选工具、调工具”的过程。
4.6 编写入口并运行验证
最后创建main.py,统一导入工具模块,然后启动 Agent。
# main.py from agent.runtime import JITAgent import tools.weather # noqa: F401 确保工具模块被导入并注册 import tools.calculator # noqa: F401 def main(): agent = JITAgent() print(agent.run("北京天气怎么样")) print("---") print(agent.run("计算 3+5")) if __name__ == "__main__": main()在项目根目录运行:
python main.py预期输出类似:
用户输入:北京天气怎么样 当前可用工具: - get_weather: 查询指定城市的天气情况 - calculate: 计算简单的四则运算表达式 动态生成计划:get_weather {'city': '北京'} 北京 今天晴,气温 25 摄氏度,空气质量良。 --- 用户输入:计算 3+5 当前可用工具: - get_weather: 查询指定城市的天气情况 - calculate: 计算简单的四则运算表达式 动态生成计划:calculate {'expression': '3+5'} 3+5 = 8到这里,一个最简 JIT-Agent 已经可以运行。接下来可以思考如何把关键词规划器替换成真正的大模型规划器。
4.7 接入大模型 function calling 的改造思路
真实生产环境中,规划器应该让大模型来承担。以常见的 function calling 接口为例,改造思路如下。
# agent/llm_planner.py # 示例思路,需按实际模型服务调整 def plan_with_llm(user_input: str, tools: list) -> list: messages = [{"role": "user", "content": user_input}] functions = [ { "type": "function", "function": { "name": tool["name"], "description": tool["description"], "parameters": tool["parameters"], }, } for tool in tools ] # 调用你使用的模型服务,这里以伪代码示意 # response = chat_completion( # model="your-model", # messages=messages, # tools=functions, # ) # # 解析响应中的 tool_calls,转换为 plan 列表 # plans = [ # { # "tool": call.function.name, # "arguments": json.loads(call.function.arguments), # } # for call in response.choices[0].message.tool_calls # ] return []核心点是:把list_tools()返回的元数据直接作为functions传给模型,模型返回工具调用计划后,执行引擎不用改动即可执行。这就是 JIT-Agent 最大的优势:工具不断新增,Agent 框架代码不变,模型每次都能看到最新的工具列表。
5. 常见问题与排查思路
下面列出 JIT-Agent 在实践中最常见的几类问题。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 工具未注册,执行时报错 | 工具模块没有被导入 | 在入口统一导入所有工具模块 |
| 大模型选错工具 | 工具描述含糊、相似工具太多 | 优化描述,增加示例,减少相似工具 |
| 参数解析报错 | function calling 返回的 JSON 格式不稳定 | 增加 JSON 解析兜底,做参数 Schema 校验 |
| 上下文超长 | 动态生成后仍然传入过多工具 | 限制候选工具数量,分批选择 |
| 并发下状态混乱 | 工具函数修改了全局状态 | 工具函数尽量无状态,避免共享可变对象 |
| 动态加载延迟较高 | 每次请求都重新分析任务 | 增加缓存、预加载热工具 |
下面逐个展开说明。
工具未注册。这是使用注册表模式最容易踩的坑。register_tool装饰器在模块导入时执行,如果某个工具模块没有被 import,它的注册代码就不会运行。解决方法是把全部工具模块在main.py中统一导入,或使用importlib按目录自动加载。
大模型选错工具。工具列表是动态生成的,但如果工具描述写得含糊,模型仍然可能选错。比如get_weather和get_temperature功能相似,模型很难区分。建议在描述里写清楚边界,必要时在参数 Schema 的description中补充示例。
参数格式不稳。不同模型对 function calling 的响应格式有差异,有的模型返回的是 JSON 字符串,有的返回结构化对象。建议在规划器中增加统一解析层,解析失败时给模型一个“重新生成”的机会,而不是直接报错。
上下文超长。JIT-Agent 已经能减少无关工具,但工具数量特别多时,即使每个工具只描述一两行,全部传给模型也会撑爆上下文。可以按工具分组或先用模型做一次粗筛,再加载候选工具的详细描述。
并发状态混乱。如果工具函数操作了全局变量,多线程场景下容易出现互相覆盖。JIT-Agent 中工具函数应该尽量保持无状态,所有输入通过参数传入,输出通过返回值带出。
动态加载延迟。每次请求都动态分析确实会带来额外耗时。在延迟敏感的场景中,可以基于历史请求做热工具缓存,把高频工具提前加载到内存。
6. 最佳实践与工程建议
6.1 工具注册表的命名与版本管理
工具名称是全局唯一标识,建议用“动词 + 名词”的格式,例如query_order、create_ticket、send_email。避免使用模糊的do_task、run之类名称。
工具数量增多后,还要考虑版本管理。可以在注册表条目中增加version字段,重要工具升级时保留旧版本,方便灰度切换。注册表本身可以增加enabled开关,让运营或开发临时下线有问题的工具,而不用重新发布代码。
6.2 配置与代码分离
JIT-Agent 的动态特性容易让人把业务逻辑写进框架代码里,这是需要避免的。工具列表、模型名称、超时时间、缓存策略等都应该放在配置文件中,通过配置中心或环境变量管理。这样工具上线、参数调整不需要改代码。
6.3 安全边界与权限控制
动态生成框架最大的风险是“工具暴露面变大”。如果所有工具都注册到一个全局注册表,一旦规划器被诱导,就可能调用危险工具。因此需要做到以下几点:
- 工具函数内部做输入校验,不轻信规划器传来的参数;
- 高危操作需要二次确认,比如删除资源、发起支付;
- 不要在生产环境使用
eval或exec,计算器示例只是为了演示原理; - 对大模型的工具选择结果做白名单校验,只允许调用当前任务允许范围内的工具;
- 对用户输入做提示注入防护,避免用户通过自然语言诱导模型调用未授权工具。
6.4 日志与可观测性
动态生成意味着每次请求的行为都可能不同,必须把规划结果记录下来。建议至少记录以下内容:
- 用户原始输入;
- 本次请求加载了哪些工具;
- 规划器生成的工具调用计划;
- 每个工具的执行结果和执行耗时;
- 如果规划器调用大模型,记录模型返回的原始响应。
有了这些日志,才能快速定位“模型选错工具”“参数错误”“工具执行异常”等问题。
6.5 性能优化
动态生成虽然灵活,但性能也需要关注。常见优化手段包括:
- 工具元数据缓存,避免每次请求都重新遍历注册表;
- 热工具预加载,把高频工具的执行函数提前放入内存;
- 规划结果缓存,对于相同或相似请求,直接复用之前的工具调用计划;
- 并发控制,避免大量请求同时触发大模型规划,导致上游服务超时。
6.6 生产环境注意事项
上线前建议先做一次“全量工具扫描”,确保所有工具模块都能正常导入、注册、执行。还要准备一个“最小工具集”兜底:当规划器没有返回任何计划,或返回的工具全部执行失败时,Agent 应该能给用户一个明确回复,而不是静默失败。
如果使用真实大模型规划器,要关注模型服务的限流和成本。可以给每次任务的规划调用设置超时时间,并在超时后降级到关键词规划器,保证核心链路可用。
7. 总结与学习路线
这篇文章主要围绕 JIT-Agent 展开,核心内容可以总结为三点:第一,JIT-Agent 是一种动态生成智能体框架的设计模型,核心是把工具选择和流程组装推迟到运行时;第二,实现一个轻量 JIT-Agent 的关键是注册表加规划器,注册表负责管理工具元数据,规划器负责按任务选择工具;第三,动态生成框架在灵活性上优于静态注册,但同时要关注安全、日志、性能和可观测性。
如果你对智能体框架感兴趣,下一步可以继续学习三块内容:一是 ReAct 模式,搞清楚 Agent 如何在大模型推理和工具调用之间循环;二是多 Agent 协作,了解多个 JIT-Agent 如何分工配合;三是工具自治,让 Agent 在运行时自动生成工具描述,甚至动态生成新的工具函数。无论走哪个方向,都可以先把本文这套最小框架跑通,再逐步往里面加入真实模型、持久化存储和更完善的工具管理能力。
实际项目中优先关注的风险点有两个:一个是工具暴露面变大带来的安全风险,另一个是动态生成导致的可观测性变差。只要把注册表权限控制和日志链路做好,JIT-Agent 就能在日常业务中发挥很大的灵活性。
如果你正在设计自己的 Agent 框架,不妨从一个小场景开始,把一两个工具接入动态注册表,跑通后再逐步扩展。如果这篇文章对你有帮助,可以收藏备用,也欢迎在评论区交流你在动态生成 Agent 时踩过的坑。