使用SKILL库
本节内容参考CaseStudy目录下的文件"LLMCaller.Table"的窗口"自动生成Foxtable代码"。
我相信大家经常使用AI自动生成代码,是不是觉得很神奇。
其实自动生成代码并不神奇,有了Foxtable的LLMCaller,我们完全可以自己实现这样的功能,而且实现的代码很简单。
要实现自动生成代码功能,首先得有指导AI生成代码的技能库。
本节就以大家经常使用的Foxtable Coder为例,我已经将这个技能库解压到CaseStudy的Coder子目录中。
技能库结构
要大模型学会Foxtable的开发技能,并生成高质量的代码,最简单直接方式是将整个Foxtable的开发指南发送给大模型的API接口。
但Foxtable的开发指南非常庞大,而大模型的上下文长度是有限制的(目前最大的也就是1M),所以需要将Foxtable的开发指南进行拆分。
Foxtable
Coder将Foxtable的开发指南按功能类别拆分成了48个单独的技能,也就是48个技能文件,为便于管理,每个技能都用一个单独的目录存放,目录名就是技能名。
AI收到用户问题后,如何知道调用哪个技能来解决问题呢? 所有除了48个技能文件,还需要一个总(路由)入口文件,对每一个技能进行介绍。
有的技能内容非常多(如事件编程),如果将全部内容都放在一个技能文件中,也很容易撑爆上下文,所以对于大型技能,技能文件只进行主要的介绍,然后将细节进行二次拆分,存放在多个参考文件。
综上所属,Foxtable Coder这个技能库的结构为:
例如:
/事件编程/SKILL.md:事件编程技能的具体说明,
/事件编程/references/01-基础知识.md:事件编程技能的参考文件之一,可能有多个。
设计思路
为了达到极致的效率和准确率,这里采用了“一脑两手”的 Agent 架构:
参考代码
打开示例文件"LLMCaller.Table"后,可以在命令窗口测试执行下面的代码:
'''Async
Dim
apiKey
As
String
=
"sk-a734b83536da45e9aedc3e42605bfb3a"
'请替换为自己的APIKey
Dim
baseDir
As
String
= System.IO.Path.Combine(ProjectPath,
"coder")
'
技能库根目录
Dim
caller
As
New
LLMCaller("https://api.deepseek.com/chat/completions",
"deepseek-v4-pro",
apiKey)
caller.Messages =
New
JArray()
' 1.
读取总控路由文件(根目录的
SKILL.md)
Dim
routerPath
As
String
= System.IO.Path.Combine(baseDir,
"SKILL.md")
Dim
routerContent
As
String
=
""
If
System.IO.File.Exists(routerPath)
Then
routerContent = System.IO.File.ReadAllText(routerPath)
Else
MessageBoxA.Show("找不到核心路由文件:SKILL.md")
Return
""
End
If
' 2.
将路由文件内容作为系统提示词(SystemPrompt)
caller.SystemPrompt =
"你是一个资深的
Foxtable
代码开发专家。"
& vbCrLf &
"下面是你的【总控路由表与语法约定】(必须严格遵守):"
& vbCrLf &
routerContent & vbCrLf & vbCrLf &
"===
行动指南
==="
& vbCrLf &
"1.
遇到用户的需求,请先根据上述【路由表】匹配对应的技能名称。"
& vbCrLf &
"2.
调用
get_skill_content
获取该技能的详细说明。"
& vbCrLf &
"3.
技能的
SKILL.md
末尾通常有一个【参考文档】表格。如果需要查阅更多细节,请调用
read_reference
读取表格中列出的对应参考文件"
' 3.
定义两个工具,分别用于读取技能文件和参考文件
With
caller.AddTool("get_skill_content",
"获取指定技能的核心
SKILL.md
说明文件。")
.AddParameter("skill_name",
"string",
"技能名称(即目录名),必须从
SystemPrompt
的路由表中提取。",
True)
End
With
With
caller.AddTool("read_reference",
"根据技能的
SKILL.md
末尾的《参考文档》表格,读取具体的参考文件内容。")
.AddParameter("skill_name",
"string",
"技能名称(即目录名)。",
True)
.AddParameter("file_name",
"string",
"参考文件名,例如
'01-基础知识.md'。",
True)
End
With
caller.ToolExecutor =
Function(ttoolName
As
String,
targs
As
JObject)
As
String
Select
Case
ttoolName
Case
"get_skill_content"
'读取技能文件
Dim
skillName
As
String
= targs("skill_name").ToString()
Dim
filePath
As
String
= System.IO.Path.Combine(baseDir, skillName,
"SKILL.md")
If
FileSys.FileExists(filePath)
Then
Return
FileSys.ReadAllText(filePath)
Else
Return
"{""error"":""未找到该技能的
SKILL.md
文件,请检查技能名是否拼写正确。""}"
End
If
Case
"read_reference"
'读取参考文件
Dim
skillName
As
String
= targs("skill_name").ToString()
Dim
fileName
As
String
= targs("file_name").ToString()
Dim
filePath
As
String
= System.IO.Path.Combine(baseDir, skillName,
"references",
fileName)
If
FileSys.FileExists(filePath)
Then
Return
FileSys.ReadAllText(filePath)
Else
Return
"{""error"":""未找到指定的参考文件。请检查文件名是否与《参考文档》表格中完全一致。""}"
End
If
Case
Else
Return
"未知工具:"
& ttoolName
End
Select
End
Function
' 4.
测试一个需要查阅参考文档的问题
caller.UserMessage =
"我想在订单表实现:输入产品编号时,自动去产品表里查找对应的品名、型号和单价并填入;输入数量时,自动计算出金额(数量*单价)"
If
caller.GetResult()
Then
MessageBoxA.Show(caller.Result)
Else
MessageBoxA.Show(caller.Error)
End
If
小提示:为提高代码生成质量,上述代码将大模型改为"deepseek-v4-pro"了。
底层发生了什么?(AI 的心路历程)
当执行这段代码时,大模型并非“一拍脑袋”就写代码,它在后台经历了极其严谨的查阅过程。如果你在代码的最后面加上一行:
Output.Show(caller.GetRequestBody().ToString())
你会看到完整的JSON请求体,其中包含了AI自主发起的两次工具调用交互:
第一轮工具调用:
查阅路由表:收到用户问题后,AI查阅系统提示词(SystemPrompt)中的路由表,发现解决这个问题需要使用“事件编程”技能。
发起调用:get_skill_content({"skill_name": "事件编程"})。
本地代码返回:“事件编程”的 SKILL.md
全文,里面包含了 e 参数说明、核心事件列表,并在末尾附带了参考文档表格。
AI 阅读后继续思考:“这属于自动计算,需要跨表引用。虽然基础说明里有一点代码,但为了确保万无一失,我要看一下底部的 05-计算.md 参考文档。”
第二轮工具调用:
5. 发起调用:read_reference({"skill_name": "事件编程", "file_name": "05-计算.md"})。
6. 本地代码返回:05-计算.md 的详细内容。
7. AI 确认:查阅到了官方最标准的写法,同时深刻记住了系统提示词里的“不要用 Sub 包裹”的红线。
大模型结合所有资料,最终输出类似下面的DataColChanged事件代码:
Dim
dr
As
DataRow = e.DataRow
'
第一部分:输入产品编号
→
从产品表自动带出品名、型号、单价
If
e.DataCol.Name =
"产品编号"
Then
If
e.NewValue
Is
Nothing
Then
'
清空产品编号时,同步清空已带出的列
dr("品名")
=
Nothing
dr("型号")
=
Nothing
dr("单价")
=
Nothing
Else
Dim
pr
As
DataRow = DataTables("产品").Find("[产品编号]
= '"
& e.NewValue &
"'")
If
pr
IsNot
Nothing
Then
dr("品名")
= pr("品名")
dr("型号")
= pr("型号")
dr("单价")
= pr("单价")
End
If
End
If
End
If
'
第二部分:输入数量或单价
→
自动计算金额
= 数量
* 单价
Select
Case
e.DataCol.Name
Case
"数量",
"单价"
If
dr.IsNull("数量")
OrElse
dr.IsNull("单价")
Then
dr("金额")
=
Nothing
Else
dr("金额")
= dr("数量")
* dr("单价")
End
If
End
Select
开发智能体软件不是梦
很多人以为智能体软件(比如
AutoGPT、Trae、Devin)是高不可攀的黑科技,但实际上,智能体的核心公式就是:
Agent = 大模型(大脑) + 上下文管理(记忆) + 工具集(手脚) + 循环执行(工作流)
我们刚才写的这套“一脑两手”架构,已经完美具备了前三个要素。如果要把它真正做成一个独立运行的智能体软件,你只需要再加三个“外挂组件”:
1. 从“只读工具”扩展到“读写执行工具”(给它真正的手脚)
我们现在的工具只有 get_skill_content(读文件)。但在真实软件中,你可以给 AI 开放更多的工具:
run_sql:允许 AI 根据分析,直接向数据库执行 Select 甚至 Update 语句。
export_excel:AI 查完数据后,直接调用 Foxtable 的 Exporter 生成报表。
send_email:AI 处理完任务后,自动发邮件通知老板。
save_data:让 AI 拥有记忆,它可以把推理的结果写进数据表或 Markdown文件里。
2. 引入“持久化记忆”(让它不会聊完就忘)
这个前面已经实现了,实现方法参考:上下文内容的持久化,本节开始提到的窗口“自动生成Foxtable代码”,就利用这个方法实现了聊天记录的持久化。
3. 用“定时器”让它拥有“心跳”(主动工作)
现在的 AI 是“你戳它一下,它动一下”。Foxtable 有强大的定时器(MyTimers)。
你可以设置每天早上 8 点自动触发一段代码,让 AI 读取最新的数据库,比对昨天的数据,主动给你写一份《昨日业务波动分析报告》发到你桌面上。当 AI
具备主动执行的能力时,它就是真正的智能体了。