Consume Kafka messages over HTTP with the Kafka Consume Policy
Create a Kafka Consume Policy that points at your Kafka brokers and topics, and attach it to an AI Agent.
A GET request to the Agent’s Route returns the messages consumed from the topics.
Prerequisites
AI Gateway running
This is a Konnect tutorial and requires a Konnect personal access token.
-
Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.
-
Export your token to an environment variable:
export KONNECT_TOKEN='YOUR_KONNECT_PAT' -
Run the AI Gateway quickstart script to automatically provision a control plane and data plane in Kong Konnect, and configure your environment:
curl -Ls https://get.konghq.com/ai | bash -s -- -k $KONNECT_TOKEN
This sets up a AI Gateway control plane named ai-quickstart, provisions a local data plane, and prints out the following environment variables export:
export AI_GATEWAY_ID=your-gateway-id
export KONNECT_TOKEN=$KONNECT_TOKEN
export KONNECT_CONTROL_PLANE_NAME=ai-quickstart
export KONNECT_CONTROL_PLANE_URL=https://us.api.konghq.com
export KONNECT_PROXY_URL='http://localhost:8000'Copy and paste these into your terminal to configure your session.
kongctl v1.20.2+
This tutorial uses kongctl to manage Konnect resources programmatically. We recommend keeping kongctl up to date with the latest version (1.20.2).
- Install kongctl from developer.konghq.com/kongctl.
-
Verify the installation:
kongctl version
kafkactl
This tutorial uses kafkactl to create topics and interact with them. Install it:
For other installation methods, see the kafkactl installation instructions.
jq
This tutorial uses jq to format JSON output. Install it:
For other installation methods, see the jq download page.
Create the working directory and SASL credentials
Create a working directory and a JAAS file that defines the SASL/PLAIN user for the broker:
mkdir -p ~/kafka-consume-test && cd ~/kafka-consume-test
cat <<'EOF' > kafka_server_jaas.conf
KafkaServer {
org.apache.kafka.common.security.plain.PlainLoginModule required
username="kafka_user"
password="kafka-password"
user_kafka_user="kafka-password";
};
EOFStart a Kafka broker
Run a single-node KRaft broker with two listeners:
- SASL on port
9092, advertised ashost.docker.internal. The data plane connects to this listener. - Plaintext on port
9094, advertised aslocalhost. Local tooling connects to this listener, becausehost.docker.internaldoesn’t resolve from the host itself.
cat <<'EOF' > docker-compose.yaml
name: kafka_aigw
services:
kafka:
image: apache/kafka:4.3.1
container_name: kafka
ports:
- "9092:9092"
- "9094:9094"
environment:
KAFKA_NODE_ID: 0
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CONTROLLER_QUORUM_VOTERS: 0@localhost:9093
KAFKA_LISTENERS: CONTROLLER://localhost:9093,SASL_PLAINTEXT://0.0.0.0:9092,PLAINTEXT://0.0.0.0:9094
KAFKA_ADVERTISED_LISTENERS: SASL_PLAINTEXT://host.docker.internal:9092,PLAINTEXT://localhost:9094
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_SASL_ENABLED_MECHANISMS: PLAIN
KAFKA_OPTS: -Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_CLUSTER_ID: 'abcdefghijklmnopqrstuv'
KAFKA_LOG_DIRS: /tmp/kraft-combined-logs
volumes:
- ./kafka_server_jaas.conf:/etc/kafka/kafka_server_jaas.conf
EOF
docker compose up -dVerify SASL authentication
Run the SASL check from a separate container so it uses the same network path as the data plane:
cat <<'EOF' > client.properties
security.protocol=SASL_PLAINTEXT
sasl.mechanism=PLAIN
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="kafka_user" password="kafka-password";
EOF
docker run --rm \
-v "$PWD/client.properties:/tmp/client.properties" \
apache/kafka:4.3.1 \
/opt/kafka/bin/kafka-broker-api-versions.sh \
--bootstrap-server host.docker.internal:9092 \
--command-config /tmp/client.propertiesThe output lists the broker API versions, including SaslHandshake(17) and SaslAuthenticate(36), without a SaslAuthenticationException.
Create the Kafka topics
Create a kafkactl context that connects through the plaintext listener, then create the ai-events and ai-audit topics:
cat <<'EOF' > kafkactl.yaml
contexts:
direct:
brokers:
- localhost:9094
EOF
kafkactl -C kafkactl.yaml --context direct create topic ai-eventsCreate the second topic:
kafkactl -C kafkactl.yaml --context direct create topic ai-auditCreate a Kafka Consume Policy and an AI Agent
Create a Kafka Consume Policy and attach it to an AI Agent that supplies the Route the Policy serves from.
This example sets security.ssl to false so the test setup doesn’t need certificates.
We don’t recommend this in production because the traffic isn’t encrypted.
cat <<'EOF' > kafka-consume.yaml
_defaults:
kongctl:
namespace: kafka-test
ai_gateway_policies:
- ref: kafka-consume
ai_gateway: !lookup {id: !env AI_GATEWAY_ID}
name: kafka-consume
display_name: Kafka Consume
type: kafka-consume
enabled: true
global: false
config:
bootstrap_servers:
- host: host.docker.internal
port: 9092
topics:
- name: ai-events
- name: ai-audit
mode: http-get
message_deserializer: json
auto_offset_reset: latest
enforce_latest_offset_reset: true
authentication:
strategy: sasl
mechanism: PLAIN
user: kafka_user
password: kafka-password
security:
ssl: false
ai_gateway_agents:
- ref: kafka-consume-agent
ai_gateway: !lookup {id: !env AI_GATEWAY_ID}
name: kafka-consume-agent
display_name: "Kafka Consume Agent"
type: http
enabled: true
policies: [ !ref kafka-consume#name ]
config:
url: http://host.docker.internal:9999
route:
paths:
- /kafka
protocols:
- http
- https
methods:
- GET
strip_path: true
EOF
kongctl sync -f kafka-consume.yaml --pat "$KONNECT_TOKEN"Validate
The Policy uses auto_offset_reset: latest, so the consumer starts at the end of each topic and only returns messages produced after it starts consuming. Prime the consumer first, then produce messages.
-
Send a first request to start the consumer. Requests sent before the data plane picks up the new configuration return
404, so wait a few seconds afterkongctl sync. The response contains no records yet:curl -s "$KONNECT_PROXY_URL/kafka" | jq . -
Produce a message to the
ai-eventstopic:kafkactl -C kafkactl.yaml --context direct produce ai-events --value='{"event":"prompt_received","model":"gpt-4o"}' -
Produce a message to the
ai-audittopic:kafkactl -C kafkactl.yaml --context direct produce ai-audit --value='{"actor":"user","action":"policy_applied"}' -
Send a second request to read the messages:
curl -s "$KONNECT_PROXY_URL/kafka" | jq .The response groups records by topic name, then by partition:
{ "ai-events": { "partitions": { "0": { "high_watermark": 1, "last_stable_offset": 1, "errcode": 0, "records": [ { "value": {"event": "prompt_received", "model": "gpt-4o"}, "key": "", "timestamp": 1787305085262, "offset": 0 } ], "aborted_transactions": {} } } }, "ai-audit": { "partitions": { "0": { "high_watermark": 1, "last_stable_offset": 1, "errcode": 0, "records": [ { "value": {"actor": "user", "action": "policy_applied"}, "key": "", "timestamp": 1787305085290, "offset": 0 } ], "aborted_transactions": {} } } } }
Cleanup
Stop the Kafka broker
docker compose downClean up AI Gateway resources
To clean up all AI Gateway resources created in this guide, run:
curl -Ls https://get.konghq.com/ai | bash -s -- -d