告警刷屏、日志翻到手酸、报告写到凌晨,这套动作网安人已经重复好几年了。直到我试着把其中一环交出去——不是交给一个“智能问答助手”,而是交给一个能按固定套路干活的Agent,才真正感觉从重复劳动里喘了口气。
这篇文章想聊的,是网安人怎么开发自己的AI Agent Skill。这里说的Skill,不是“帮我分析这个日志”这种一次性对话,而是一个可以把告警研判、情报查证、报告生成这类动作封装成标准化能力、能被Agent反复调用的数字资产。
适合谁来读?和我一样在安全运营、应急响应、渗透测试或安全开发岗位上待过、手上攒了一堆重复性工作的人。哪怕你现在还在用AI助手当“高级搜索框”,这篇文章也能帮你把思路从“问问题”切换到“建工具”。
1. 先搞清楚Agent Skill到底是个什么东西
1.1 不是写个提示词就叫Skill
很多人以为Skill就是一段精心设计的提示词,这是一个容易踩的认知误区。提示词写在对话框里,每次都要人来组织、人来触发,它本质上还是一次对话;Skill是给Agent消费的能力单元,它包含的远远不止提示词。
一个完整的Skill,至少要有四块:
- 能力声明:用一段简洁的文字告诉Agent“我是干什么的、什么情况下调用我”,这段声明决定模型会不会在合适的时机选中它。
- 参数契约:描述调用这个Skill需要哪些输入字段,每个字段的类型、含义、是否必填,这部分通常用JSON Schema表达。
- 执行逻辑:要么是纯本地代码对输入做处理,要么是大模型按某种提示词模板来推理,多数情况下是两者结合的编排流程。
- 输出格式:规定返回结果是JSON对象还是结构化文本,字段怎么定,方便Agent把结果继续传给下一个环节。
理解这件事有一个很直观的对比。你让AI“分析这段Web访问日志”,它顶多给你一段泛泛的解读;但如果你给它一个“Web访问日志异常研判Skill”,它会按你预设的步骤来:先解析日志成结构化字段,再提取源IP和URL特征,然后查信誉库、比对攻击特征,最后按固定格式输出风险评分和研判依据。
差别不在于模型变聪明了,而在于你把“怎么分析”这件事变成了可执行的规格说明。Skill的价值,就是把个人脑子里的判断流程物化出来。
1.2 安全运营里最适合封装成Skill的几类任务
不是所有工作都值得封装成Skill。我自己的判断标准有三条:高重复、有明确输入输出、存在可固化的判断规则。
按这个标准,下面几类任务是目前最值得动手的:
| 任务类型 | 典型场景 | 为什么适合 |
|---|---|---|
| 告警研判 | 防火墙/WAF/EDR告警的真伪与优先级判断 | 重复性极高,规则可固化,输出需要结构化 |
| 日志解构 | 从原始日志里提取IP、域名、文件哈希、时间线 | 输入输出明确,纯解析逻辑,适合规则+模型组合 |
| 情报聚合 | 查询威胁情报、提取IOC、判断情报相关度 | 有固定流程,输出需要统一格式 |
| 报告生成 | 把处置过程转成事件报告、周报要点 | 格式固定,内容来自结构化数据,边际成本低 |
| 知识检索 | 从内部知识库/历史案例里找相似处置方案 | 检索 + 归纳,适合RAG形态的Skill |
选场景有个窍门:先找那个你每周都要做、每次做都觉得“其实套路都一样”的事。那个事就是你的第一个Skill。
2. 网安人开发Skill的选型思考
2.1 先定边界:哪些事交给规则,哪些事交给模型
我刚接触Skill开发时常犯一个错误:试图让大模型做所有事,包括解析日志这种格式完全固定的活。结果模型偶尔会把字段看漏,或者把时间格式改得千奇百怪。
后来想明白一个原则:确定性的事归代码,判断性的事归模型。
日志解析、格式转换、正则匹配、情报接口请求,这些有唯一正确答案的事,全部写成代码,放在Skill的执行逻辑里,不经过大模型。大模型只负责它擅长的事:归纳、判断、解释、决策建议。
以告警研判为例,编排逻辑大致是这样:
- 本地代码接收原始告警文本,用正则和规则解析出源IP、目标IP、告警类型、时间、资产信息等字段。
- 本地代码调用威胁情报接口,拿到IP信誉、历史攻击记录、关联样本等信息。
- 把结构化字段和情报结果拼成一段上下文,交给大模型。
- 大模型按预设的研判框架输出结论,包括风险等级、判断依据、建议动作。
这样设计的好处是,模型不会因为日志里有一行异常格式就发挥失常,代码已经把底线兜住了。规则的职责是保下限,模型的职责是拉上限。
2.2 从“人能干的流程”里提炼输入输出契约
做Skill的第一步,不是写代码,而是把你自己干活的流程拆出来。找个安静的下午,拿一个真实的告警,按你平时的动作一步一步走一遍,然后写下来。
拿告警研判举例子,你实际会做的事大概是:
- 看告警类型和规则命中内容
- 看源IP是谁、目标资产是什么、是不是核心业务
- 查源IP有没有历史信誉问题
- 看告警时段有没有其他关联行为
- 判断是真攻击还是误报,给出建议
把这些动作转成Skill的输入和输出定义。
输入字段:
{ "alert_text": "原始告警文本,未做任何清洗", "asset_info": "目标资产信息,包括业务归属、重要性等级,可为空", "threat_intel": "外部情报查询结果,可为空", "context": "可选的上文补充信息" }输出字段:
{ "risk_score": "0到100的数值,越高风险越大", "verdict": "tweet|suspicious|malicious|confirmed", "evidence": "支持判断结论的关键证据列表", "reasoning": "决策依据的自然语言描述", "suggested_actions": "建议处置动作列表" }这个过程看起来简单,但绝大多数人不会认真做。我见过不少做了一半跑去问“该用什么框架”的人,其实问题不在框架,在于压根没想清楚Skill的输入输出长什么样。
2.3 开发时的“最小闭环”心态
第一次做Skill,一定要控制规模。做一个覆盖全场景的“全能研判Skill”,不如先做一个只处理某一种告警类型的垂直Skill。
我的推荐路径是:挑一个你手上最高频、最让你烦的告警类型,做一个只处理它的Skill。目标不是覆盖所有场景,而是让这个单一场景的处理从“每次都要看一遍”变成“一键跑完、结果可解释”。
跑通之后你会得到三样东西:一个能用的Skill、一套测试样例、一份“这个Skill还不会做什么”的边界清单。有这三样,你后续扩展才有依据。一上来就想做平台级的东西,大概率做不完,也做不好。
3. 完整实操:做一个告警研判Skill
3.1 场景定义与预期效果
直接用我做过的一个例子来说明。当时的需求是处理一个开源检测引擎输出的登录类告警,每天有上百条,大部分是扫描器在撞库,小部分是业务账号异常登录,偶尔有真正的失陷迹象。每次手工研判的流程很固定,但很花时间。
我期望的效果是:把当天所有告警文本一次性丢给Agent,它调用Skill后能输出一张表,每条告警一行,包含风险等级、判断结论、依据和建议动作。我再花五分钟扫一眼,把高风险的挑出来看,其余的直接进低优先级队列。
这个预期决定了Skill的设计方向:批量处理、结构化输出、每条判断都要有依据。不是要一个“看看这是什么告警”的对话工具,而是要一个能出活的生产工具。
3.2 定义功能描述与参数
Skill的能力声明,是给Agent看的,要写得足够明确,让它知道什么时候该调用你。
功能描述:分析登录类告警,判断是否为扫描器探测、暴力破解、异常登录或账户失陷, 输出风险评分、研判结论与处置建议。适用于批量告警研判场景,输入为原始告警文本列表。然后是参数契约。这一步要特别注意字段的description,模型理解参数语义基本靠它,写得越具体,模型越不会乱传参数。
{ "name": "login_alert_triage", "description": "对登录类安全告警进行批量研判,输出结构化风险结论", "parameters": { "type": "object", "properties": { "alerts": { "type": "array", "description": "告警文本列表,每个元素是一条原始告警,尽量包含时间、源IP、目标账号、登录结果等信息", "items": { "type": "string" } }, "asset_list": { "type": "object", "description": "资产信息映射,key为目标资产标识,value为该资产的业务重要性和历史风险情况,可选", "additionalProperties": { "type": "object" } } }, "required": ["alerts"] } }参数设计上有两个容易忽略的点。一是不要设计过多可选参数,可选参数一多,模型调用时的决策负担就大,出错概率会上升。二是数组类型的参数要强调“每条元素是原始告警”,否则模型可能会自作聪明先帮你把告警摘要一遍再传进来,这样会丢细节。
3.3 提示词主体:把研判逻辑写进Skill
Skill内部的提示词,和平时聊天用的提示词不太一样。聊天的提示词追求引导模型自由发挥,Skill的提示词追求的是约束模型在固定框架内输出。我用的研判框架通常包含这几部分:
你是安全运营专家,负责对登录类告警进行研判。 请严格按以下步骤分析每条告警: 步骤一:提取关键线索 - 源IP、目标账号、时间、登录结果(成功/失败) - 线索之间是否有逻辑矛盾 步骤二:结合外部上下文判断 - 如果告警显示短时间内多个账号从同一IP登录,优先判断为扫描探测 - 如果同一个账号从多个地理区域登录且成功,优先判断为异常登录 - 如果存在情报显示的恶意IP,结合情报活跃度给出结论 步骤三:给出结论 按统一格式输出JSON对象,不得输出任何解释性文字: { "alert_index": 告警在输入列表中的序号, "risk_score": 0-100整数, "verdict": "scan_probe|brute_force|anomalous_login|compromised|benign", "evidence": [关键证据,最多3条], "reasoning": "约50字以内的判断依据说明", "suggested_actions": [处置建议,最多2条] }写这段提示词有几个心得。
第一条,要让模型“先枚举线索,再下结论”。直接让它判断“这是不是攻击”,它很容易跳过推理过程凭感觉给答案;让它先提取线索、再对照规则,输出质量会明显变好。
第二条,规则要写“如果……那么……”的具体形式,不要写“请结合上下文综合判断”这种空话。模型对明确条件分支的遵循度,远高于对模糊指令的遵循度。
第三条,输出格式必须写死。我不允许它输出多余的解释文字,否则批量处理时没法用程序解析。格式化输出这件事,要从Skill第一版就定死,后面改格式的代价很大。
3.4 接入Agent的本地编排逻辑
Skill不只是提示词加参数契约,它还需要一段可执行的编排逻辑。这里我用一个Python风格的伪代码来说明执行链路:
def run_login_alert_triage(alerts, asset_list=None): results = [] for alert in alerts: # 第一步:本地解析,不经过大模型 parsed = parse_login_alert(alert) # 正则提取时间、源IP、账号、结果 if parsed is None: results.append({"alert_index": alerts.index(alert), "error": "无法解析该告警,请人工查看"}) continue # 第二步:本地查情报,拼上下文 intel = query_intel(parsed["src_ip"]) # 返回信誉分、历史标签 context = build_context(parsed, intel, asset_list) # 第三步:调用模型推理,按Skill提示词执行 conclusion = llm_invoke(parse_template(), context) # 第四步:本地固化格式,确保字段完整 results.append(validate_output(conclusion)) return build_final_json(results)这套编排逻辑有几个关键设计。
解析和情报查询全部走本地代码,只有推理部分才调用模型。这样能保证Skill在大模型不可用时,至少能完成解析和情报聚合,也方便对模型输出做校验。
validate_output这一步很多人会忽略。模型偶尔会返回缺字段的JSON,或者把枚举值写成别的单词,必须有一层本地校验兜底,不满足要求的让模型重试或者标记为需要人工关注。
执行的时候,我建议把告警分组批量喂给模型,而不是一条一条调用。批量处理既省时间和费用,也能让模型在对比多条告警时更容易发现规律。一次喂10到20条是比较舒服的量,再多模型容易在长上下文里丢失细节。
4. 让Skill从“能用”变成“好用”
4.1 用测试集压测Skill
写一个Skill再快也就半天,难的是让它稳定地好用。我做的第一版告警研判Skill,看起来逻辑完整,实际一跑全是问题:有的告警没有日期字段,模型楞猜了一个时间;有的源IP是内网地址,模型依然去查情报;还有的用例输出JSON格式不完整。
从那以后我养成习惯:每个Skill都维护一个测试集,至少20条真实历史样本,覆盖正常样本、边界样本、脏样本三种类型。正常样本是格式规整的告警,边界样本是缺少字段或字段值异常的告警,脏样本是夹杂了乱码、换行、编码混乱的原始日志。
测试方法不复杂:把测试集反复跑,跑完逐条对照预期输出,看三样东西:格式合法性、结论稳定性、定位准确性。格式问题靠校验器自动查;结论稳定性做两三次对比,看同一条告警反复跑结论会不会跳;定位准确性靠自己对历史结果的记忆来判断。
压测会暴露一个常被忽略的问题:模型输出和你的“判断直觉”不一致。不一定是模型错,有可能是你自己的判断规则本身有模糊地带。这时候不要急着改提示词,先把规则分歧记录成案例,作为下一轮迭代的依据。
4.2 通过反馈循环迭代Skill
Skill的迭代优化,核心是形成一个反馈闭环:运行记录 → 错误分类 → 规则修正 → 重新测试。
我第一次压测后发现,模型对“多个账号短时间内从同一IP成功登录”这种情况,有时判成暴力破解,有时判成异常登录,非常不稳定。翻了几条输出后,发现问题出在提示词里没定义“短时间”和“多个账号”的量化阈值。
修正方案是在提示词里加了一条:如果同一源IP在10分钟内出现超过3个不同账号的登录成功记录,判为异常登录。改完之后,这类情况的判断稳定多了。
另一个高发错误是模型会过度依赖IP情报,把纯内网扫描判成高风险。修正方法是允许本地代码识别内网地址段,在内网IP的情况下跳过情报查询,并在上下文中明显标注“该IP为内网地址,情报参考价值低”。
这类迭代要留记录,每版Skill改了什么、为什么改、对应哪条测试样例,都要写清楚。没有记录的迭代,三个月后你会不知道自己改了什么,也无法判断改动到底解决了什么问题。
4.3 把Skill当代码来管理
Skill的开发时间一长,自然会产生版本管理的需求。我的做法是把它当做一个标准的代码仓库来管理。
仓库结构大致长这样:
login_alert_triage/ ├── skill.md # 功能说明与调用描述 ├── prompt.md # 推理提示词模板 ├── schema.json # 参数契约定义 ├── executor.py # 本地执行逻辑 ├── config.yaml # 阈值、情报接口、模型参数配置 ├── tests/ │ ├── cases.json # 历史样本集 │ ├── expected.json # 预期输出标注 │ └── run_tests.py # 自动化测试脚本 └── CHANGELOG.md # 迭代记录把阈值、接口地址、模型温度这类配置全部抽到配置文件里,不要硬编码在代码里。这样做的好处是调整参数时不用动代码,也方便在不同环境间复用。
版本管理不只是Git层面的管理,更重要的是语义层面的管理:Skill的能力边界、适用范围、已知问题,都要有文字记录。
5. 从Skill到数字资产:网安人怎么沉淀自己的东西
5.1 数字资产的三个层面
真正在长期使用中发挥作用的东西,可以拆成三个层面:技能库、数据集、评估集。这三者互为基础,缺一不可。
技能库是你封装的Skill本体,也就是前面说的那套代码加提示词加配置。它是你的“生产资料”,可以复制、移植、升级。
数据集是你跑过的大量脱敏样本。包括各类告警日志、事件案例、情报查询记录,数据经过去标识化处理后存放。这些样本是后续优化Skill的重要原料。
评估集是带预期标注的测试样本。每一条都有“该输出什么结论、不该输出什么结论”的标注,是保证Skill回归测试可靠性的核心资产。
三者之间的关系可以用一句话概括:技能库是产品,数据集是原材料,评估集是质检标准。只有产品没有质检标准的开发,还停留在“能用就行”的阶段。
5.2 做一份“可迁移”的Skill文档
技术人普遍不爱写文档,但Skill的文档偏偏很关键。我不赞成写长篇大论,但建议每个Skill都有一份结构化的说明文档,至少包含七个部分:
- 目标:这个Skill解决什么场景下的什么问题
- 输入:需要哪些数据,数据从哪里获取,格式是什么
- 输出:结构化输出的字段定义和取值说明
- 依赖:需要哪些外部接口、哪些本地环境
- 边界:哪些情况这个Skill不处理,处理不了的如何降级
- 测试记录:历次压测的结论、覆盖率和已知问题
- 迭代记录:每个版本改了什么、为什么改、验证结果
先别嫌麻烦,这套文档最关键的两个部分是“边界”和“测试记录”。边界写清楚什么情况下不能指望这个Skill,能避免后续使用者把期望放到一个错误的位置上;测试记录建立一个基本可信度,让后面的人知道哪些情况是被验证过的。
我有个习惯:Skill的第一版完成时,就顺手把“边界”写掉。趁开发时对限制记忆清晰的时候写,比过两个月再补要省太多力气。
5.3 复用时的三个注意点
Skill做成资产后,迟早要拿给别人用,或者在别的项目里复用。三个经验值得分享。
第一个是参数里默认不要带敏感信息。Skill的默认配置里如果包含了真实的API地址、内部账号口令或特定业务系统的标识,一旦复制出去,成本和风险都会放大。正确做法是让所有环境相关的内容都通过配置文件注入,Skill本体保持干净。
第二个是命名和注释要有延续性。内部变量叫做a1、b2的Skill,过三个月连自己都看不懂。变量名直接使用安全领域习惯的说法,比如source_ip、alert_type、threat_level,别人接手时成本会低很多。
第三个永远是“场景隔离”。同一个Skill在不同场景的表现不一定一致。在一个环境里表现很好的规则,换到另一个网络环境里可能完全失效。复用的时候必须重新跑一遍测试集,不要直接信任它在新环境的表现。
6. 网安人做Agent Skill,常见的坑
6.1 把“方法论”当成Skill
最常见的错误,是把一段“如何做应急响应”的方法论文本直接当成Skill。方法论有价值,但它不是可执行的能力单元。
Skill需要明确的输入输出、可调用的执行逻辑和可验证的结果。方法论这些都不具备。拿“安全运营方法论”当Skill,Agent拿到后会无所适从,因为它不知道自己应该干什么。
我自己界定的方式是问一句:如果我给它一组真实数据,它能给我一个可以验证的结果吗?能,就是Skill;不能,就只是参考资料。
6.2 期望大模型记住所有上下文
大模型有上下文窗口,窗口再大也是有限的。很多人在设计Skill时,试图把丰富的信息全部塞进提示词,一次性解释所有规则逻辑和场景分支。
后果是提示词冗长、模型抓不住重点。处理分析时,模型容易遗漏规则,该套用的分支没有套用,输出质量不升反降。
我自己总结的经验是:提示词只写核心判断规则,其余信息通过本地代码处理后以“上下文数据”的形式提供。模型只需要关注眼前这份处理好的数据,不用关心数据从哪来。
6.3 让Skill运行依赖一个假设可用的外部服务
Skill如果需要外部服务的支持,设计时就必须考虑那个服务万一不可用怎么办。比如情报查询接口超时,整个Skill是不是就会卡死。
我最初做的Skill依赖一个外部情报接口,接口响应不稳定,经常导致输出结果时间长达几十秒,严重时直接失败了。后来改成“接口超时降级”:本地设置超时时间并缓存查询结果,接口不可用时降级为只依据本地规则判断并给出标注“未获取外部情报,结果置信度有限”。
外部依赖的健壮性,本质上是Skill生产可用性的保障,这层功夫不能省。
6.4 只做Skill不建评估集
没有评估集的Skill,就像没有测试用例的程序,功能看着在跑,但质量没有度量。
我见过不少人,Skill做完就上线用了,效果也不错,还沾沾自喜。直到有一次在正式环境用了Skills,莫名其妙出了几条离谱的判断,才意识到之前压根没有建立一套反馈评测机制。那些“效果不错”的判断,很可能是在特定输入分布下碰巧出来的假象。
所以我的建议是:Skill开发的最后一步,永远是建立评估集,而且越早越好,最好从开发第一天就开始积累。这样每次改完Skill可以做回归测试,判断改进到底有没有效果,而不是靠印象和感觉。
6.5 忽视批量处理时的成本控制
最后一个坑和钱有关。Skill一旦批量处理数据,单次推理成本会迅速累积。
我最初做告警批量推理时,没有做任何成本抑制设计,结果处理几千条告警跑下来费用较高。后来加了几层控制:最大批量长度限制、重复告警去重、简单规则前置过滤,把“肯定没问题”的告警直接在本地过滤掉,只让模型处理真正需要判断的那部分,成本大幅度降低了。
成本控制不是不花钱,而是把钱花在需要花的地方。如果你只是用AI做一次性对话,这件事不用考虑;但如果要拿它当生产线工具,这条建议值得记在心里。
我自己做了十几个Skill之后,最大感受是:Skill不是核心,过程中被逼着把模糊的判断逻辑一步步显性化才是核心。你会开始注意到原来自己处理告警时,有些判断依赖的是经验中说不清的直觉,而把它们条文化、规则化、可测试化,正是数字资产形成的过程。
如果你也想试,我的建议很简单:挑你手上最烦的那件重复性工作,先不要管架构和框架,用一个周末的时间把它做成一个能跑的“小东西”。哪怕只有一条判断规则,哪怕只能处理一种情况,你也已经上路了。