实习手记:从 ELK 到 Agent 的"最后一公里"------告警消费与上下文富化实战
背景碎碎念 :上一篇博客梳理完了 AI SOC 的整体宏观架构,老板和导师看后觉得方向没问题,接下来就是"撸起袖子干活"的落地阶段了。这几天我负责的模块正是整个系统的"咽喉要道"------如何把 Elasticsearch (ELK) 里冷冰冰、乱糟糟的原始告警,安全、高效、有条理地送到 DeepAgents 的大模型"嘴里"。今天就来详细复盘一下这段时间的踩坑与实战经验。
01. 痛点:为什么"从 ELK 到 Agent"没你想的那么简单?
在理想的 Demo 里,我们可能会觉得:不就是从 Elasticsearch 里查出一条告警,然后扔给 LangChain 的 LLM 去处理吗?
但真正到了生产环境,我才发现这里面全是细节和坑:
- 字段爆炸与噪音太多 :一条标准的 ELK 告警(比如 EDR 报出的某台主机进程注入告警),里面往往包含几十甚至上百个字段(
_index,_id,agent.id,host.ip,process.command_line,user.name等等)。如果把整个原始 JSON 毫无过滤地塞给 LLM,不仅会瞬间吃光你的 Token 额度,还会引入大量的"噪音字段",干扰 LLM 的注意力。 - 时效性与消费机制的选择:ELK 里的数据是持续写入的。我们是用定时轮询(Polling)去拉取?还是用 Logstash/Webhook 触发?怎么保证告警既不漏掉,也不会被重复消费导致 AI 重复研判?
- 上下文缺失 (Context Deficiency) :Elasticsearch 里的单条告警往往是"孤立"的。LLM 拿到"某 IP 有可疑外联"时,它只知道这个 IP,但它不知道这台机器半小时前还发生过什么(比如有没有中过钓鱼邮件、是不是核心业务服务器)。如果缺乏上下文富化,AI 的研判往往流于表面。
为了解决这些问题,我们在架构上设计了一套告警消费与上下文富化流水线 (Alert Ingestion & Context Enrichment Pipeline)。
02. 告警数据消费与富化流向设计
为了让团队内部对这个模块的数据流向达成共识,我画了第一张架构细化图。

在这张图中,数据流经了三个关键阶段:
- 数据监听与拉取层:通过自定义的 Python 异步消费脚本,对接 Elasticsearch 的 Scroll API 或 Query 接口,实时捕获符合严重级别的安全告警。
- 清洗与标准化层:过滤掉无用的系统元数据字段,只保留安全研判最关心的核心要素(如:事件类型、时间戳、源/目 IP、主机名、进程路径、用户名)。
- 富化层 (Enrichment):在送给 Agent 之前,自动去补充一些背景信息(例如通过内部 CMDB 补充资产等级,或者拉取该 IP 最近 1 小时内的历史关联日志)。
03. 代码落地:我是怎么写这个消费与清洗模块的?
在具体的代码实现上,我们使用 Python 编写了这套消费逻辑。这里抽离出一个简化版的架构逻辑,记录一下我是怎么把杂乱的日志变成整洁的上下文的。
1. 定义标准化的告警结构 (Pydantic)
为了让 LangChain 框架和后续的 Agent 工具能完美识别,我们首先用 Pydantic 定义了一个强类型的数据结构:
python
from pydantic import BaseModel, Field
from typing import Optional, List
class SecurityAlert(BaseModel):
alert_id: str = Field(description="告警唯一标识符")
timestamp: str = Field(description="告警发生时间")
severity: str = Field(description="告警严重程度: Low, Medium, High, Critical")
attack_technique: Optional[str] = Field(description="MITRE ATT&CK 战术/技术编号")
# 实体信息
source_ip: Optional[str] = Field(description="源IP地址")
destination_ip: Optional[str] = Field(description="目的IP地址")
hostname: Optional[str] = Field(description="受影响主机名")
username: Optional[str] = Field(description="操作用户名")
process_name: Optional[str] = Field(description="触发告警的进程名")
# 上下文补充
asset_importance: Optional[str] = Field(default="Normal", description="资产重要性: Core, Important, Normal")
historical_hits_count: int = Field(default=0, description="该IP近期历史告警次数")
2. 清洗与富化核心逻辑 (Data Pipeline)
接下来是消费端的核心代码逻辑。我们从 Elasticsearch 中把原始 JSON 拿出来后,通过一个清洗函数进行字段映射和动态富化:
python
class AlertConsumerPipeline:
def __init__(self, es_client, cmdb_client):
self.es = es_client
self.cmdb = cmdb_client
def process_raw_alert(self, raw_es_doc: dict) -> SecurityAlert:
source = raw_es_doc.get("_source", {})
# 1. 字段映射与清洗 (只留精华,抛弃噪声)
alert_id = raw_es_doc.get("_id")
timestamp = source.get("@timestamp")
severity = source.get("rule", {}).get("level", "Medium")
# 提取网络与主机实体
src_ip = source.get("source", {}).get("ip")
dst_ip = source.get("destination", {}).get("ip")
hostname = source.get("host", {}).get("name")
username = source.get("user", {}).get("name")
process_name = source.get("process", {}).get("name")
# 2. 上下文富化 (Enrichment)
# 调用内部 CMDB 接口查询主机资产重要性
asset_info = self.cmdb.query_by_hostname(hostname) if hostname else {}
asset_importance = asset_info.get("importance", "Normal")
# 查询 Elasticsearch 历史关联记录 (比如看这个 IP 最近有没有频繁告警)
history_count = self.query_recent_alert_count(src_ip) if src_ip else 0
# 3. 组装成结构化对象
structured_alert = SecurityAlert(
alert_id=alert_id,
timestamp=timestamp,
severity=severity,
source_ip=src_ip,
destination_ip=dst_ip,
hostname=hostname,
username=username,
process_name=process_name,
asset_importance=asset_importance,
historical_hits_count=history_count
)
return structured_alert
def query_recent_alert_count(self, ip: str) -> int:
# 模拟在 ES 中聚合查询该 IP 过去 24 小时的历史告警数
# 实际业务中这里会写一个标准的 ES Aggregation Query
return 3
04. LLM 提示词模板的组装
告警清洗完后,怎么把这些结构化数据变成 DeepAgents 能听懂的自然语言/结构化 Prompt?这里我们又踩过坑:如果直接给一堆 JSON,大模型在长文本或多轮对话时容易漏掉关键参数。
为此,我们设计了一套面向安全研判的 Prompt 模板结构。

通过这种分层的 Prompt 设计,当 DeepAgents 拿到数据时,它不仅知道"发生了什么告警",还一眼能看出"这台机器是核心资产(Asset Importance: Core)"以及"这个 IP 已经连续 3 次触发告警了",从而在第一步的推理(Reasoning)中就赋予极高的优先级。
05. 实习小结与下一步
通过这一阶段的开发,我深刻体会到了在安全领域做 AI 工程化落地的一个核心真理:垃圾进,垃圾出 (Garbage in, Garbage out)。
大模型虽然聪明,但如果你给它的 ELK 原始数据杂乱无章,它根本无法做出精准的安全研判。通过定义严格的 Pydantic 模型、清洗无用字段、并在消费端做资产与历史日志的"富化(Enrichment)",我们才真正为后面的 Agent 自动化研判打下了坚实的地基。
地基打好后,接下来就是最激动人心的部分了------怎么给这个 Agent 装上"眼睛"和"耳朵",让它能够自主去调用外部的威胁情报工具(如 VirusTotal、内部情报库)?