This guide shows you how to enrich OCSF events with AI-generated summaries,
classifications, and annotations by using ai::prompt.
The ai::prompt operator sends one request per input event to an
OpenAI-compatible Responses API endpoint. Use it when a deterministic rule or
lookup table is too rigid, and keep the prompt payload small and explicit. The
examples use compact OCSF-style records as from {...} starting points so you
can focus on the enrichment pattern.
OCSF events have an enrichments field for inline enrichment data associated
with an event or finding. The examples write the model result to a temporary
field, use add to add an OCSF enrichment object to enrichments,
and then remove the temporary field.
Summarize a detection finding
Section titled “Summarize a detection finding”Use data to send only the fields that the model needs. This example enriches
an OCSF detection_finding event with a one-sentence summary and adds it to
the event’s enrichments field:
from { time: 2024-08-22T09:12:34, category_uid: 2, class_uid: 2004, activity_id: 1, activity_name: "Create", type_uid: 200401, severity_id: 4, message: "Suspicious PowerShell command downloaded a script from a newly registered domain.", metadata: { version: "1.8.0", }, finding_info: { uid: "finding-42", title: "Suspicious PowerShell download", desc: "PowerShell used a hidden window to download and execute a remote script.", }, enrichments: [], confidence_id: 3, confidence: "High",}ai::prompt model="qwen3", system="Summarize this OCSF detection finding in one sentence for a security analyst.", data={ severity_id: severity_id, confidence: confidence, message: message, finding: finding_info, }, into=ai.summarywhere ai.summary != nullenrichments = enrichments.add({ name: "finding_info.uid", value: finding_info.uid, type: "ai_summary", provider: "Tenzir ai::prompt", short_desc: ai.summary.text, src_url: "http://127.0.0.1:11434/v1/responses", created_time: now(), data: { text: ai.summary.text, model: ai.summary.model, usage: ai.summary.usage, latency_ms: ai.summary.latency / 1ms, },})drop aiThe default endpoint is http://127.0.0.1:11434/v1, which matches the standard
Ollama OpenAI-compatible API port. The example works with a local model if
Ollama is running and the qwen3 model is available.
Classify DNS activity
Section titled “Classify DNS activity”For fields that you want to use later in a pipeline, ask for a compact answer. The operator stores the model response as text and doesn’t parse JSON-looking output into structured fields.
This example classifies an OCSF dns_activity event. It sends the DNS query,
response, and endpoints instead of the entire event:
from { time: 2024-08-22T09:13:01, category_uid: 4, class_uid: 4003, activity_id: 2, activity_name: "Response", type_uid: 400302, severity_id: 1, message: "DNS response for suspicious-update.example resolved to 198.51.100.42.", metadata: { version: "1.8.0", }, query: { hostname: "suspicious-update.example", }, rcode_id: 0, rcode: "NoError", src_endpoint: { ip: 10.10.2.17, port: 54213, hostname: "workstation-17", }, dst_endpoint: { ip: 192.0.2.53, port: 53, hostname: "resolver-1", }, answers: [{ rdata: "198.51.100.42", type: "A", ttl: 60, }], enrichments: [],}ai::prompt model="qwen3", system="Classify this OCSF DNS event as benign, suspicious, or malicious. Reply with one label and a short reason.", data={ query: query.hostname, rcode: rcode, answers: answers, src_endpoint: src_endpoint, dst_endpoint: dst_endpoint, }, into=ai.classification, timeout=10s, concurrency=4where ai.classification != nullenrichments = enrichments.add({ name: "query.hostname", value: query.hostname, type: "ai_classification", provider: "Tenzir ai::prompt", short_desc: ai.classification.text, src_url: "http://127.0.0.1:11434/v1/responses", created_time: now(), data: { text: ai.classification.text, model: ai.classification.model, usage: ai.classification.usage, latency_ms: ai.classification.latency / 1ms, },})drop aiUse concurrency to control how many requests can run at the same time. Keep
the value low until you understand your provider’s rate limits and your model
latency.
Use a hosted endpoint
Section titled “Use a hosted endpoint”To use a hosted OpenAI-compatible endpoint, set endpoint and api_key. Both
arguments are resolved as secrets, so you can keep provider details out of the
pipeline text:
from { time: 2024-08-22T09:14:10, category_uid: 2, class_uid: 2004, activity_id: 2, activity_name: "Update", type_uid: 200402, severity_id: 3, message: "Detection confidence increased after related network activity.", metadata: { version: "1.8.0", }, finding_info: { uid: "finding-43", title: "Suspicious domain contact", desc: "A workstation contacted a domain that appeared in a related DNS activity event.", }, enrichments: [], confidence_id: 2, confidence: "Medium",}ai::prompt model="gpt-4.1-mini", endpoint=secret("openai-endpoint"), api_key=secret("openai-api-key"), system="Suggest one next investigation step for this OCSF finding.", data={ severity_id: severity_id, confidence: confidence, message: message, finding: finding_info, }, into=ai.next_stepwhere ai.next_step != nullenrichments = enrichments.add({ name: "finding_info.uid", value: finding_info.uid, type: "ai_next_step", provider: "Tenzir ai::prompt", short_desc: ai.next_step.text, src_url: "https://api.openai.com/v1/responses", created_time: now(), data: { text: ai.next_step.text, model: ai.next_step.model, usage: ai.next_step.usage, latency_ms: ai.next_step.latency / 1ms, },})drop aiHandle request failures
Section titled “Handle request failures”If a request fails, Tenzir keeps the input event and writes null to the
temporary result field. Filter or route failed enrichments before you add an
OCSF enrichment object:
where ai.next_step != nullModel calls can send sensitive event data to another process or service. Prefer
data={...} over sending the whole event, omit fields that aren’t needed for
the task, and use secrets for endpoint URLs and API keys.