k8s-agent架构思考(一)
背景思考
LLM 大模型横空出世之后,一切都变得不一样了。以前写程序,所有逻辑都得靠人一行行写,参数对参数,一步都马虎不得。但现在突然冒出来一个"东西",你问它什么,它就能答什么,而且回答得既准又深,简直像换了个时代。
你可以把 LLM 想象成一个黑盒子,往里输自然语言,往外吐自然语言,就像身边随时有个导师,总能告诉你该怎么做。而 Function Call 则让这个指导变得更精准------你给它一堆工具清单和说明,它会根据你的问题告诉你该调哪个工具;等工具跑完出结果,你再把结果扔回给 LLM,让它综合起来给出最终答案。
既然有这么牛的"神导师",那我们是不是可以围着它来做各种程序上的决策呢?
llm模型是监督学习的,训练的时候,输入一大段文字,输出为这大段文字的下一个文字,然后不断循环.
基础agent程序模块划分
既然我们要围绕这位"神导师"形成一个系统,那必然得有分工。
我们常说 Agent 是一段程序,那这段程序又该如何与 LLM 更好地协作呢?
一个基础的思考:

最接近大模型的模块 - provider模块
总览
模块的核心就是调大模型, 可以多种方式, 常见的是http, openai,另外有些厂商有自己的方式和客户端库,直接使用即可
如deepseek文档:
HTTP

openai库

这里要说明一下,OpenAI 的库其实是一个通用的客户端库,并不是说只能调用 OpenAI 的服务端。因为 GPT 出现得比较早,它的调用规范后来被很多其他大模型也兼容了,所以很多时候你只需要把
base_url换成对应模型的 API 地址就行,比如这里用的是 DeepSeek 的地址。当然,像 Gemini、Anthropic 这些也有自己的调用方式,国内厂商同样如此,你可以根据自己的需求选择接入。
流程
核心参数说明
model ,对应的模型,厂商的官网找
Messages,
实际上这个就是对话消息,
role分几类 :
- System 系统prompt
会把一段加载到content中, 参考:
Shell
**# 系统角色**
你是一个智能 Agent 的任务执行模块。你需要根据 Planner 制定的步骤序列,逐步执行任务,并最终向用户输出结果。
**# 任务描述**
1. 接收 Planner 提供的子任务列表以及用户最初的诉求。
2. 按照步骤顺序,依次完成每个任务的 `task_desc`,确保产出符合 `expected_output` 的要求。
3. 在执行过程中,若需要使用外部工具(如搜索、计算器等),请按规定格式调用工具;若无需工具,则利用你的内在知识直接处理。
4. 当执行到最后一步(通常是总结步骤)时,你需要将前面步骤的产出进行综合汇总,并严格按照 ****Markdown**** 格式输出最终的完整回复给用户。
**# 执行规则**
- 保持上下文连贯性,将前置步骤的产出作为后续步骤的输入。
- 内容都用中文输出
- 只有在执行到最后一步,或需要调用外部工具时,才向外输出对应信息。非最后一步的纯思考与推演过程在内部完成即可,无需直接展示给用户。
- 最后一步的最终输出必须是结构清晰、排版良好的 Markdown 文本,直接面向最终用户。
**# 对应的markdown 格式输出规范:**
一、总体原则
结构清晰:使用层级标题组织内容,逻辑递进,避免大段无结构文本。
视觉分明:代码、表格、列表、引用等元素须与正文明确区分,便于阅读与复制。
专业克制:全文不使用任何 emoji 或表情符号,语气客观、严谨。
语义准确:Markdown 语法使用规范,不滥用加粗、斜体等强调手段。
二、标题层级
使用 # 至 #### 四级标题,严禁使用五级及以下标题。
一级标题(#)仅用于文档总标题,每篇输出最多出现一次。
二级标题(##)用于主要章节划分,三级(###)用于小节,四级(####)用于具体条目。
标题前后须空一行,与正文保持呼吸感。
三、段落与正文
每段文字控制在 3--5 行以内,超过时按逻辑拆分。
段与段之间空一行,严禁连续多行无内容空行。
关键术语首次出现时可用 **加粗** 标注,同一文档内重复出现时不再加粗。
长句避免堆砌,适当使用分号、编号列表拆分复杂信息。
四、代码块
所有代码、命令、配置、JSON、伪代码等须放入 fenced code block(```)。
必须标注语言标识符,如 ```python、```bash、```json。
代码块前后各空一行,与正文隔离。
行内代码(`code`)仅用于极短变量名、函数名或参数值,禁止包裹整句。
五、表格
数据对比、参数说明、步骤映射等场景优先使用表格。
表格须包含表头,表头与内容行使用 |---|---| 明确分隔。
表格前后各空一行,表格内文字左对齐,数字右对齐。
复杂表格可在表头后加一行简短说明,置于表格上方,格式为:*表 X:XXX*。
六、列表
同级并列项使用无序列表(-),层级缩进 2 个空格。
有序步骤使用有序列表(1.、2.),步骤间若有补充说明可嵌套无序列表。
列表项内容较长时,项内可分段,但保持缩进一致。
七、引用与分隔
重要提示、注意事项、免责声明使用引用块(> ),单行或多行均可。
章节之间如需强分隔,可使用水平线(---),但全篇不超过 2 处。
八、禁止事项
全文禁用 emoji、颜文字、ASCII 艺术及其他非功能性符号。
禁用 HTML 标签(如 <br>、<center>)实现排版。
禁用无意义的加粗或斜体堆砌,避免视觉噪音。
九、最终检查清单
输出前逐项确认:
[ ] 标题层级未超过四级
[ ] 代码块均带语言标识
[ ] 表格有表头且对齐规范
[ ] 无 emoji 及表情符号
[ ] 段落间有空行,无连续空行
[ ] 专业术语首次出现已加粗
**# 示例**
假设你收到的步骤为:
...
- User 用户问题
例子:
JSON
{"role": "user", "content": "广州明天天气怎样", "reason": null, "attachments": null, "tool_calls": null, "tool_call_id": null, "tool_name": null, "timestamp": "2026-07-16T16:57:53.986416", "metadata": {}}
- Assistant llm返回
当前llm返回一般是直接content返回,如果要求agent去调用工具,则会返回 tool调用
留意下面的 tool_calls:\[\] 这是返回列表,让agent具体调哪个方法,参数是什么
JSON
{"role": "assistant", "content": " ", "reason": "用户想知道广州明天的天气情况。我需要先获取当前日期和时间,然后再搜索广州明天的天气预报。\n\n让我先获取当前时间。", "attachments": null, "tool_calls": [{"id": "call_00_ecjW6uksa5tqRcEG6UOj7111", "name": "time-server__get_current_time", "arguments": {"timezone": "Asia/Shanghai", "format": "iso"}}], "tool_call_id": null, "tool_name": null, "timestamp": "2026-07-16T16:58:04.802830", "metadata": {"node_name": "executor"}}
上面的返回是代码里封装了下,实际上openai客户库是返回了delta对象

- Tool 工具返回内容
agent调用了工具,然后把内容贴上来
如下实际上也是以文本的形式贴到了content中,后续llm看到这段文本就知道内容了
SQL
{"role": "tool", "content": "{\n \"time\": \"2026-07-15T04:40:08.000Z\",\n \"timezone\": \"Asia/Shanghai\",\n \"format\": \"iso\"\n}", "reason": null, "attachments": null, "tool_calls": null, "tool_call_id": "call_00_66Vs1z6zBL3vtVrMABvl3165", "tool_name": "time-server__get_current_time", "timestamp": "2026-07-15T12:40:08.153858", "metadata": {}}
一段会话如下:
这里没有存system_prompt, 一般不存,方便自己的会话消息可以给其它的agent直接使用

为什么token会消耗多,因为每一轮调用llm都会把大段地历史也结合告诉llm.
Tools
上面messages有说到llm有时会返回让agent去调用工具, 所以同时要让llm知道有哪些工具可用
传入llm的tools是个数组, 如下,cos-mcp 是腾讯云cos 的mcp服务
SQL
[
{
'type': 'function',
'function': {
'name': 'list_all_tools',
'description': '列出当前所有可用的工具的名称列表(不包含自身)',
'parameters': {
'properties': {},
'title': 'ListToolsParams',
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'cos-mcp__getCosConfig',
'description': '获取COS配置, 腾讯云配置',
'parameters': {
'properties': {},
'title': 'MCPTool_Cos_mcp_Getcosconfig_Params',
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'cos-mcp__putObject',
'description': '上传本地文件到存储桶',
'parameters': {
'properties': {
'filePath': {
'description': '文件路径 (包含文件名)',
'title': 'Filepath',
'type': 'string'
},
'fileName': {
'default': None,
'description': '文件名 (存在存储桶里的名称)',
'title': 'Filename',
'type': 'string'
},
'targetDir': {
'default': None,
'description': '目标目录 (存在存储桶的哪个目录)',
'title': 'Targetdir',
'type': 'string'
}
},
'required': ['filePath'],
'title': 'MCPTool_Cos_mcp_Putobject_Params',
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'cos-mcp__putString',
'description': '上传字符串内容到存储桶',
'parameters': {
'properties': {
'content': {
'description': '要上传的字符串内容',
'title': 'Content',
'type': 'string'
},
'fileName': {
'description': '文件名 (存在存储桶里的名称)',
'title': 'Filename',
'type': 'string'
},
'targetDir': {
'default': None,
'description': '目标目录 (存在存储桶的哪个目录)',
'title': 'Targetdir',
'type': 'string'
},
'contentType': {
'default': None,
'description': '内容类型,如 text/plain, application/json 等,默认为 text/plain',
'title': 'Contenttype',
'type': 'string'
}
},
'required': ['content', 'fileName'],
'title': 'MCPTool_Cos_mcp_Putstring_Params',
'type': 'object'
}
}
}
.....
问题
现在能调 llm了, 有了大脑, 那就需要 四肢了,于是就有了tool模块
agent自己的能力 - tool模块
流程

上面的 3,4,5,6 可能会不断循环, 这种思考, 行动,观察, Agent = LLM + 工具(Tools) 可以做为一个整体, react方式
所以简化一下

其它
mcp是什么? mcp实际上就是工具组,如 高德amap 有这些工具可用,
比如下面这个是调用 tavily-search 这个mcp的 search 工具, 留意 tool_calls中的 name, arguments
JSON
{"role": "assistant", "content": " ", "reason": "The user is asking about the weather in Meizhou (梅州) for tomorrow (7/17) and the day after tomorrow (7/18). Let me search for this information.", "attachments": null, "tool_calls": [{"id": "call_00_wBELeSxD3v6fothJgijM5324", "name": "tavily-search__tavily_search", "arguments": {"query": "梅州 2026年7月17日 7月18日 天气预报", "search_depth": "advanced", "max_results": 5}}], "tool_call_id": null, "tool_name": null, "timestamp": "2026-07-16T16:58:53.645023", "metadata": {"node_name": "executor"}}

问题:
Agent 有了 tools 确实能干不少事,但问题是 tool 本身太原子化了。比如我想让 Agent 帮我规划某天去某个景点,它得先查天气、查交通、介绍景点、提醒注意事项、推荐酒店......这一套下来太综合了,总不能每次都重新拆一遍吧?
那能不能把这些步骤存起来,下次直接用这个流程?当然可以。所以就有了 Skill 这个概念。
经验组合- skills模块
流程
流程上我们还是上面的react 方式, 只不过会加一个tool, 加载skill, 然后通过这个skill文本来指导react的流程
所以虽然上加了个模块,但结构是没变的!!!
例子:
Markdown
---
name: scenic-spot-guide
version: 1.0.0
description: 当用户查询某个旅游景区(古镇、古城、自然景区等)时,自动搜集该景区的全面信息,并按结构化模板生成一份图文并茂的 Markdown 旅游攻略,涵盖景点概览、交通指南、门票信息、核心景点详解(含历史特色)、美食推荐、行程建议、住宿推荐、最佳旅游季节、特产伴手礼等完整内容。
author: Agent
tags:
- 旅游
dependencies:
- 工具
input:
- scenic_name: 景区名称
---
**# 景区查询攻略生成器 (Scenic Spot Guide)**
**## 核心功能**
1. ****景区基本信息获取****:搜集景区的地理位置、历史沿革、荣誉称号(如世界遗产、5A级景区等)
2. ****核心景点详解****:列出景区内所有主要景点/子景点,逐一说明其历史年代、建筑特色、文化价值、传奇故事等
3. ****实用信息整理****:门票价格、优惠政策、开放时间、交通路线(飞机/高铁/火车/汽车)
4. ****游玩攻略生成****:推荐行程路线(1日/2日/3日)、最佳游览顺序
5. ****周边配套信息****:美食推荐、住宿建议、特产伴手礼、最佳旅游季节
6. ****名人/文化轶事****:到访过的名人、相关的历史故事或民间传说
**## 输入参数**
| 参数名 | 类型 | 必填 | 说明 |
| ---------------- | ------------- | ---- | ------------------------------------------------------------------------------ |
| `scenic_name` | string | 是 | 景区名称,如"平遥古城"、"丽江古城"、"故宫"等 |
| `user_questions` | array[string] | 否 | 用户额外关心的问题列表,如["有哪些名人去过", "门票多少钱"],将作为补充搜索方向 |
**## 输出结果**
返回一份结构完整的 Markdown 旅游攻略,包含以下模块(按顺序):
1. ****概览**** - 景区名称、地理位置、历史年限、荣誉称号、世界遗产/5A等评级
2. ****交通指南**** - 飞机、高铁、火车、汽车等到达方式及注意事项
3. ****门票信息**** - 价格、优惠政策(免票/半票人群)、开放时间
4. ****核心景点详解**** - 每个景点的名称、年代、历史特色、建筑风格、文化价值、趣味故事等
5. ****城外/周边景点****(若有)
6. ****必看演出/体验****(若有)
7. ****美食推荐****
8. ****推荐行程路线****(按天数排列)
9. ****住宿建议****
10. ****最佳旅游季节****(含气候数据)
11. ****特产伴手礼****
12. ****名人到访/文化轶事****(若用户关注)
**## 依赖环境**
- 网络搜索工具(用于搜集景区最新信息,如 Tavily Search)
- 时间工具(用于获取当前时间,判断季节等)
- 文件系统(用于读取已有数据或保存输出)
**## 使用示例**
```markdown
用户输入:查一下平遥古镇
触发技能:scenic-spot-guide
参数:{ "scenic_name": "平遥古城" }
输出:# 🏛️ 平遥古城 · 完整旅游攻略 ## 📍 概览
... ## 🚗 交通指南
... ## 🎫 门票信息
...
(完整的 Markdown 攻略)
用户输入:有哪些名人去过平遥古城
触发技能:scenic-spot-guide
参数:{
"scenic_name": "平遥古城",
"user_questions": ["有哪些名人去过"]
}
输出:在攻略中补充「名人到访」章节,详列到访过的国家领导人、外国政要、文化名人、本地出身名人等。
**## 执行流程**
**### Step 1:搜索景区基本信息**
- 使用搜索工具搜索景区名称 + "旅游攻略 景点介绍"
- 获取景区的地理位置、历史沿革、荣誉称号
**### Step 2:搜索核心景点详细信息**
- 搜索景区内具体景点列表 + "详细列表 历史特色 介绍"
- 对主要景点逐一获取年代、历史背景、建筑特色
**### Step 3:搜索实用信息**
- 门票价格、优惠政策、开放时间、交通路线
**### Step 4:搜索附加信息(按用户需求)**
- 若用户问了特定问题(如名人、美食、天气),单独搜索补充
- 搜索"景区 名人 去过 参观 明星 领导"等
**### Step 5:整合输出**
- 将所有信息按输出模板整合为结构化 Markdown 文档
- 确保排版清晰、图文并茂、信息准确
**## 注意事项**
1. 门票价格等时效性信息以最新搜索结果为准,注意标注信息来源时间
2. 若景区含通票(多个子景点联票),需详细列出通票包含的景点清单
3. 每个核心景点应包含:名称、年代、历史地位/特色、值得看的点、趣味故事(如有)
4. 对于有世界遗产称号的景区,需说明登录年份和联合国评价
5. 若用户查询的是"古镇/古城"类景区,需特别关注其保存状况、建筑风格、文化底蕴
6. 行程推荐应根据景点数量和分布给出合理的1~3日方案
7. 所有信息需核实准确性,避免过时或错误信息
8. 最终输出为纯 Markdown 格式,不附加额外解释文字
**## 引用资源**
无外部引用资源。
**## 代码文件清单**
- `SKILL.md` - 技能定义文件(本文件)
问题:
这三个要素------Provider、tools、skills------确实是使用LLM的基础骨架。但在实际场景中,尤其是面对复杂任务时,光有这些还不够。我们需要一套灵活的编排机制来管理多轮交互、工具调用和分支决策。
以客服系统为例,用户的请求可以分为三类:
-
打招呼(如"Hi"):直接返回问候,无需任何工具介入。
-
简单任务(如查询订单状态):可能需要调用一次或两次工具(查数据库、整理结果),然后判断是否还需更多工具,最终给出答案。
-
复杂任务(如处理退换货流程):需要拆解成多个子步骤,每个步骤可能依赖前一步的结果,并且可能在不同节点上调用不同工具,甚至需要条件判断和循环。
针对这类多步骤、带状态的编排需求,节点编排是一种非常自然的解决方案。
编排- Graph 模块
这边我们习惯使用 LangGraph 这个开源工具来做编排,它能让流程搭建更直观灵活。
流程
整体上 ReAct 模式保持不变,但我们可以在此基础上加入编排逻辑:先分析用户意图,再决定后续路径。举个例子,如果你希望在最终输出前对文本做一轮润色或格式化,可以额外加一个"文本输出节点",把 ReAct 的完整输出作为输入交给这个节点处理。像这样的节点都可以根据实际业务需求自由组合,按需编排就好。
实际编排中要考虑多个内容, 每个节点负责一件事:
入口节点:接收用户输入。
判断节点:让 LLM 根据用户意图做分类(比如"打招呼""简单任务""复杂任务")。
工具调用节点:执行具体的 tool(查天气、查交通等)。
LLM 推理节点:让模型根据上下文生成下一步指令或最终回复。
循环/条件跳转:比如简单任务调完工具后,再次判断是否需要更多工具;复杂任务则按 Skill 定义的步骤依次执行,每步都可能回到判断节点。
结束节点:输出最终答案
问题:
这样就能回答一些复杂问题了,如果想用户使用更好一点,也可以考虑加上memory等.
用户的记忆-memory,session模块
似skills 把用户的memory加载到messages中
memory是在会话中压缩的内容, 这个需要额外处理.
总结
基础的 Agent 搭建起来后,核心结构就是 Provider 和 Tool,其他部分都属于扩展。可以把 LLM 看作大脑,Tool 看作爪子,这样一个"八爪鱼式"的 Agent 就诞生了。
如果是在本地使用,安全性基本可控。可以在本地添加各种 MCP、Tools 等组件------像 Claude Code 这类工具就是这样做的。虽然它们的权限较大,但只要做好权限控制并谨慎操作,风险是可以接受的。
但如果 Agent 部署在服务器上,安全问题就会变得比较突出。因为用户可能会安装各种各样的工具,有些甚至能通过 exec 直接执行本地命令(例如 Python),进而查看环境变量(其中可能包含密钥)。这样一来,用户的问题就可能演变成类似 SQL 注入的安全风险。