From 021990a6ee0e6e1fd89fb027ac030aa80cd53c65 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Mon, 5 Jan 2026 19:47:15 -0700 Subject: [PATCH 01/97] DOC-1867 AI Gateway # Conflicts: # modules/ROOT/nav.adoc --- modules/ROOT/nav.adoc | 2 + modules/ai-agents/pages/ai-gateway.adoc | 164 ++++++++++++++++++++++++ modules/shared/images/ai-gateway.png | Bin 0 -> 211123 bytes 3 files changed, 166 insertions(+) create mode 100644 modules/ai-agents/pages/ai-gateway.adoc create mode 100644 modules/shared/images/ai-gateway.png diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index ce168d2ab..a985d235a 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -87,6 +87,8 @@ **** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] **** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] **** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] +*** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] +** xref:ai-agents:ai-gateway.adoc[] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] diff --git a/modules/ai-agents/pages/ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway.adoc new file mode 100644 index 000000000..351727938 --- /dev/null +++ b/modules/ai-agents/pages/ai-gateway.adoc @@ -0,0 +1,164 @@ += AI Gateway Quickstart +:description: Learn how to configure the AI Gateway for Redpanda Cloud, including the LLM proxy and the MCP proxy. +:page-beta: true + +[NOTE] +==== +This private beta documentation introduces AI Gateway in a *Self-Managed* deployment for internal testing. However, it will be released for *Redpanda Cloud BYOC* deployments only. This is a living document, and the UX/API will evolve quickly. +==== + +The Redpanda AI Gateway is a production-grade proxy that provides unified access to multiple Large Language Model (LLM) providers and Model Context Protocol (MCP) servers through a single endpoint. It maintains centralized control over routing, rate limiting, cost optimization, security, and observability. + +== Prerequisites + +* Access to the AI Gateway UI (provided by your administrator) +* API key for at least one LLM provider (OpenAI, Anthropic, or AWS Bedrock) +* (Optional) MCP server endpoints if you plan to use tool aggregation + +== Get started + +Before users can create gateways, an administrator must enable LLM providers and models. + +=== Step 1: Enable a provider + +Providers represent upstream services (Anthropic, OpenAI, AWS Bedrock, custom) and associated credentials. Providers are disabled by default. An administrator must enable them explicitly by adding credentials. + +. Navigate to *Providers*. +. Select a provider (for example, *Anthropic*). +. On the *Configuration* tab, enter your API Key. + +=== Step 2: Enable models + +The model catalog is the set of models made available through the gateway. Models are disabled by default. An administrator must enable them explicitly. + +The infrastructure that is serving the model is different based on the provider you select. For example, AWS Bedrock has different reliability and availability metrics than Anthropic. When you consider all the metrics, you can design your gateway to use different providers for different use cases. + +. Navigate to *Models*. +. Enable the models you want exposed through gateways. + +NOTE: Requests use the `vendor/model_id` format (for example, `openai/gpt-4o`, `anthropic/claude-3-5-sonnet-20241022`). + +=== Step 3: Create a gateway + +A gateway is a logical configuration boundary (policies + routing + observability) on top of a single deployment. It's a "virtual gateway" that you can create per team, environment (staging/production), product, or customer. + +. Navigate to *Gateways*. +. Click *Create Gateway*. +. Choose a name, workspace, and optional metadata. +. After creation, copy the *Gateway Endpoint* from the gateway detail page. + +TIP: A _workspace_ is conceptually similar to a _resource group_ in Redpanda streaming. + +=== Step 4: Configure LLM routing + +On the Gateways page, select the *LLM* tab to configure rate limits, spend limits, and routing policies. + +The LLM routing pipeline visually represents the request lifecycle: + +. Rate Limit (first): For example, global rate limit of 100 requests/second +. Spend Limit / Monthly Budget (second): For example, $15K/month with blocking enforcement +. Routing to a primary provider pool with optional fallback provider pool(s): For example, primary route to Anthropic pool, fallback to Bedrock pool + +*Load balancing / multi-provider distribution:* +If a provider pool contains multiple providers, you can distribute traffic (for example, balancing across Anthropic and Bedrock). + +TIP: Provider pool (UI) = Backend pool (API) + +=== Step 5: Configure MCP tools + +NOTE: Model Context Protocol (MCP) is a standard for connecting AI agents to external tools and data sources. MCP servers expose tools that agents can discover and call. + +On the Gateways page, select the *MCP* tab to configure tool discovery and tool execution. + +You can aggregate multiple MCP servers behind a single endpoint. For example: + +* Data catalog API +* MCP orchestrator +* Research memory store +* Vector search service + +*How MCP works:* + +* You configure MCP server endpoints in the MCP gateway. +* The gateway presents a single aggregated MCP surface to the agent. +* Agents can list/search tools and call them through the gateway. + +*MCP orchestrator* + +The orchestrator is a built-in MCP server that enables programmatic tool calling. The agent can generate JavaScript to call multiple tools in a single orchestrated step, which reduces the number of round trips. For example, a workflow requiring 47 file reads can be reduced from 49 round trips to just 1. + +=== Step 6: Understand tiered tool loading (token savings) + +When many tools are aggregated, listing all tools can consume significant tokens. Tiered tool loading effectively behaves as tiered/lazy tool discovery: + +* Instead of returning all tools, the MCP gateway initially returns: +** a *tool search* capability, and +** the *MCP orchestrator* +* The agent then searches for the specific tool it needs and retrieves only that subset. + +This can reduce token usage significantly (for example, 80-90% depending on how many servers/tools are configured). + +== Observability + +After traffic flows through a gateway, you can inspect: + +* Request volume +* Token usage +* Estimated spend +* Latency +* Per-model breakdown + +This is central to governance: You can see and control usage by gateway boundary (for example, by team, environment, customer, or product). + +== CEL routing + +The AI Gateway uses Common Expression Language (CEL) for flexible routing and policy application. CEL expressions let you create sophisticated routing rules based on request properties without code changes. Use CEL to: + +* Route requests to specific providers based on model family +* Apply different rate limits based on user tiers +* Enforce policies based on request content + +An inline editor in the UI helps you discover available request fields (headers, path, body, and so on). + +=== Practical CEL examples + +Route based on model family: + +[,cel] +---- +request.body.model.startsWith("anthropic/") +---- + +Apply a rule to all requests: + +[,cel] +---- +true +---- + +Route based on a header (for example, product tier): + +[,cel] +---- +request.headers['tier'][0] == "premium" +---- + +Guard for field existence: + +[,cel] +---- +has(request.body.max_tokens) && request.body.max_tokens > 1000 +---- + +== Architecture + +The AI Gateway is the policy-controlled choke point for both LLM inference and tool access in agentic systems. It is a core building block in the Agentic Data Plane: Agents reason, plan and execute, invoking LLMs and tools, while logs, metrics, and traces flow to the customer's observability stack. + +image::shared:ai-gateway.png[AI Gateway architecture] + +== Common gateway patterns + +* *Team isolation*: Create separate gateways for each team to track usage and enforce budgets independently. +* *Environment separation*: Use different gateways for staging and production with appropriate rate limits. +* *Failover*: Configure a primary provider pool with a fallback pool for high availability. +* *A/B testing*: Distribute traffic across providers to compare performance and cost. \ No newline at end of file diff --git a/modules/shared/images/ai-gateway.png b/modules/shared/images/ai-gateway.png new file mode 100644 index 0000000000000000000000000000000000000000..0754146d5d0268750155ab550e490c1119e4ae35 GIT binary patch literal 211123 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

P=>R*cf*+is!GCyAXov|_9VpCjQB|Cj+2 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

  • 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 literal 0 HcmV?d00001 From 5a9cc4bced52e910a89adb9b6aa200ffc8dc5e20 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Mon, 5 Jan 2026 21:09:14 -0700 Subject: [PATCH 02/97] add questions for reviewers --- modules/ai-agents/pages/ai-gateway.adoc | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway.adoc index 351727938..2988d0199 100644 --- a/modules/ai-agents/pages/ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway.adoc @@ -1,11 +1,6 @@ = AI Gateway Quickstart -:description: Learn how to configure the AI Gateway for Redpanda Cloud, including the LLM proxy and the MCP proxy. -:page-beta: true +:description: Learn how to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. -[NOTE] -==== -This private beta documentation introduces AI Gateway in a *Self-Managed* deployment for internal testing. However, it will be released for *Redpanda Cloud BYOC* deployments only. This is a living document, and the UX/API will evolve quickly. -==== The Redpanda AI Gateway is a production-grade proxy that provides unified access to multiple Large Language Model (LLM) providers and Model Context Protocol (MCP) servers through a single endpoint. It maintains centralized control over routing, rate limiting, cost optimization, security, and observability. @@ -17,7 +12,7 @@ The Redpanda AI Gateway is a production-grade proxy that provides unified access == Get started -Before users can create gateways, an administrator must enable LLM providers and models. +Before a gateway owner can create a gateway, an administrator must enable LLM providers and models. === Step 1: Enable a provider @@ -36,7 +31,7 @@ The infrastructure that is serving the model is different based on the provider . Navigate to *Models*. . Enable the models you want exposed through gateways. -NOTE: Requests use the `vendor/model_id` format (for example, `openai/gpt-4o`, `anthropic/claude-3-5-sonnet-20241022`). +NOTE: Requests must use the `vendor/model_id` format (for example, `openai/gpt-4o`, `anthropic/claude-3-5-sonnet-20241022`). Requests that omit the vendor prefix may be rejected. === Step 3: Create a gateway @@ -57,7 +52,7 @@ The LLM routing pipeline visually represents the request lifecycle: . Rate Limit (first): For example, global rate limit of 100 requests/second . Spend Limit / Monthly Budget (second): For example, $15K/month with blocking enforcement -. Routing to a primary provider pool with optional fallback provider pool(s): For example, primary route to Anthropic pool, fallback to Bedrock pool +. Routing to a primary provider pool with optional fallback provider pools: For example, primary route to Anthropic pool, fallback to Bedrock pool *Load balancing / multi-provider distribution:* If a provider pool contains multiple providers, you can distribute traffic (for example, balancing across Anthropic and Bedrock). @@ -87,6 +82,8 @@ You can aggregate multiple MCP servers behind a single endpoint. For example: The orchestrator is a built-in MCP server that enables programmatic tool calling. The agent can generate JavaScript to call multiple tools in a single orchestrated step, which reduces the number of round trips. For example, a workflow requiring 47 file reads can be reduced from 49 round trips to just 1. +*REVIEWERS: When/how exactly do you use the orchestrator? Also what happens after they create a gateway? Please provide an example of how to validate end-to-end routing against the gateway endpoint!* + === Step 6: Understand tiered tool loading (token savings) When many tools are aggregated, listing all tools can consume significant tokens. Tiered tool loading effectively behaves as tiered/lazy tool discovery: @@ -110,6 +107,8 @@ After traffic flows through a gateway, you can inspect: This is central to governance: You can see and control usage by gateway boundary (for example, by team, environment, customer, or product). +*REVIEWERS: Where do those metrics appear in the UI, or how does a user validate observability after setup?* + == CEL routing The AI Gateway uses Common Expression Language (CEL) for flexible routing and policy application. CEL expressions let you create sophisticated routing rules based on request properties without code changes. Use CEL to: From 8188598ec13d989db2fc40a01f494a62f8d72930 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Tue, 6 Jan 2026 13:02:38 -0700 Subject: [PATCH 03/97] incorporate review comments --- modules/ROOT/nav.adoc | 4 +- modules/ai-agents/pages/ai-gateway.adoc | 66 ++++++++++++------ modules/ai-agents/pages/index.adoc | 8 +-- .../{images => partials}/ai-gateway.png | Bin 4 files changed, 48 insertions(+), 30 deletions(-) rename modules/shared/{images => partials}/ai-gateway.png (100%) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index a985d235a..500dccbda 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -69,7 +69,8 @@ ** xref:security:secrets.adoc[Secrets] ** xref:security:cloud-safety-reliability.adoc[Safety and Reliability] -* xref:ai-agents:index.adoc[AI Agents] +* xref:ai-agents:index.adoc[Agentic Data Plane] +** xref:ai-agents:ai-gateway.adoc[] ** xref:ai-agents:mcp/overview.adoc[MCP Overview] ** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] *** xref:ai-agents:mcp/local/overview.adoc[Overview] @@ -88,7 +89,6 @@ **** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] **** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] *** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] -** xref:ai-agents:ai-gateway.adoc[] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] diff --git a/modules/ai-agents/pages/ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway.adoc index 2988d0199..1e77e9709 100644 --- a/modules/ai-agents/pages/ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway.adoc @@ -2,13 +2,15 @@ :description: Learn how to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. +NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. + The Redpanda AI Gateway is a production-grade proxy that provides unified access to multiple Large Language Model (LLM) providers and Model Context Protocol (MCP) servers through a single endpoint. It maintains centralized control over routing, rate limiting, cost optimization, security, and observability. == Prerequisites * Access to the AI Gateway UI (provided by your administrator) -* API key for at least one LLM provider (OpenAI, Anthropic, or AWS Bedrock) -* (Optional) MCP server endpoints if you plan to use tool aggregation +* API key for at least one LLM provider: OpenAI or Anthropic +* Optional: MCP server endpoints if you plan to use tool aggregation == Get started @@ -16,22 +18,47 @@ Before a gateway owner can create a gateway, an administrator must enable LLM pr === Step 1: Enable a provider -Providers represent upstream services (Anthropic, OpenAI, AWS Bedrock, custom) and associated credentials. Providers are disabled by default. An administrator must enable them explicitly by adding credentials. +Providers represent upstream services (Anthropic, OpenAI) and associated credentials. Providers are disabled by default. An administrator must enable them explicitly by adding credentials. . Navigate to *Providers*. -. Select a provider (for example, *Anthropic*). +. Select a provider (for example, Anthropic). . On the *Configuration* tab, enter your API Key. === Step 2: Enable models The model catalog is the set of models made available through the gateway. Models are disabled by default. An administrator must enable them explicitly. -The infrastructure that is serving the model is different based on the provider you select. For example, AWS Bedrock has different reliability and availability metrics than Anthropic. When you consider all the metrics, you can design your gateway to use different providers for different use cases. +The infrastructure that is serving the model is different based on the provider you select. For example, OpenAI has different reliability and availability metrics than Anthropic. When you consider all the metrics, you can design your gateway to use different providers for different use cases. . Navigate to *Models*. . Enable the models you want exposed through gateways. -NOTE: Requests must use the `vendor/model_id` format (for example, `openai/gpt-4o`, `anthropic/claude-3-5-sonnet-20241022`). Requests that omit the vendor prefix may be rejected. +==== Model naming convention + +Model provider requests must use the `vendor/model_id` format in the model property of the request body, and include the `rp-aigw-id` header with the gateway ID the request is being sent to. The following example routes OpenAI API calls through Redpanda's AI Gateway for centralized control. + +[source,python] +---- +# Example: Using the OpenAI Python SDK with AI Gateway +from openai import OpenAI + +client = OpenAI( + base_url="https://gw.ai.panda.com", <1> + api_key="your-api-key", +) + +# Add header per request +response = client.chat.completions.create( + model="openai/gpt-5", <2> + messages=[{"role": "user", "content": "Hello!"}], + extra_headers={ + "rp-aigw-id": "gateway-abc" # Override for this request + } <3> +) +---- +<1> This redirects the OpenAI client to the AI Gateway endpoint. +<2> The `model` property uses the `vendor/model_id` format as required by the AI Gateway. +<3> Includes the `rp-aigw-id` header to specify which gateway configuration to use. === Step 3: Create a gateway @@ -40,22 +67,23 @@ A gateway is a logical configuration boundary (policies + routing + observabilit . Navigate to *Gateways*. . Click *Create Gateway*. . Choose a name, workspace, and optional metadata. -. After creation, copy the *Gateway Endpoint* from the gateway detail page. - ++ TIP: A _workspace_ is conceptually similar to a _resource group_ in Redpanda streaming. +. After creation, copy the *Gateway Endpoint* from the gateway detail page. + === Step 4: Configure LLM routing On the Gateways page, select the *LLM* tab to configure rate limits, spend limits, and routing policies. The LLM routing pipeline visually represents the request lifecycle: -. Rate Limit (first): For example, global rate limit of 100 requests/second -. Spend Limit / Monthly Budget (second): For example, $15K/month with blocking enforcement -. Routing to a primary provider pool with optional fallback provider pools: For example, primary route to Anthropic pool, fallback to Bedrock pool +. Rate Limit: For example, global rate limit of 100 requests/second +. Spend Limit / Monthly Budget: For example, $15K/month with blocking enforcement +. Routing to a primary provider pool with optional fallback provider pools: For example, primary route to Anthropic pool, fallback to OpenAI pool *Load balancing / multi-provider distribution:* -If a provider pool contains multiple providers, you can distribute traffic (for example, balancing across Anthropic and Bedrock). +If a provider pool contains multiple providers, you can distribute traffic (for example, balancing across Anthropic and OpenAI). TIP: Provider pool (UI) = Backend pool (API) @@ -63,7 +91,7 @@ TIP: Provider pool (UI) = Backend pool (API) NOTE: Model Context Protocol (MCP) is a standard for connecting AI agents to external tools and data sources. MCP servers expose tools that agents can discover and call. -On the Gateways page, select the *MCP* tab to configure tool discovery and tool execution. +On the Gateways page, select the *MCP* tab to configure tool discovery and tool execution. You can aggregate multiple MCP servers behind a single endpoint. For example: @@ -84,9 +112,9 @@ The orchestrator is a built-in MCP server that enables programmatic tool calling *REVIEWERS: When/how exactly do you use the orchestrator? Also what happens after they create a gateway? Please provide an example of how to validate end-to-end routing against the gateway endpoint!* -=== Step 6: Understand tiered tool loading (token savings) +=== Step 6: Understand deferred tool loading (token savings) -When many tools are aggregated, listing all tools can consume significant tokens. Tiered tool loading effectively behaves as tiered/lazy tool discovery: +When many tools are aggregated, listing all tools can consume significant tokens. Deferred tool loading effectively behaves as lazy tool discovery: * Instead of returning all tools, the MCP gateway initially returns: ** a *tool search* capability, and @@ -119,7 +147,7 @@ The AI Gateway uses Common Expression Language (CEL) for flexible routing and po An inline editor in the UI helps you discover available request fields (headers, path, body, and so on). -=== Practical CEL examples +=== CEL examples Route based on model family: @@ -149,12 +177,6 @@ Guard for field existence: has(request.body.max_tokens) && request.body.max_tokens > 1000 ---- -== Architecture - -The AI Gateway is the policy-controlled choke point for both LLM inference and tool access in agentic systems. It is a core building block in the Agentic Data Plane: Agents reason, plan and execute, invoking LLMs and tools, while logs, metrics, and traces flow to the customer's observability stack. - -image::shared:ai-gateway.png[AI Gateway architecture] - == Common gateway patterns * *Team isolation*: Create separate gateways for each team to track usage and enforce budgets independently. diff --git a/modules/ai-agents/pages/index.adoc b/modules/ai-agents/pages/index.adoc index 9ac867a96..b5a5737db 100644 --- a/modules/ai-agents/pages/index.adoc +++ b/modules/ai-agents/pages/index.adoc @@ -1,8 +1,4 @@ -= AI Agents in Redpanda Cloud -:description: Learn about AI agents and the tools Redpanda Cloud provides for building them. += Agentic Data Plane +:description: Learn about the Redpanda Agentic Data Plane, including the AI Gateway, AI agents, and MCP servers. :page-layout: index :page-aliases: develop:agents/about.adoc, develop:ai-agents/about.adoc - -AI agents are configurable assistants that autonomously perform specialist tasks by leveraging large language models (LLMs) and connecting to external data sources and tools. - -Redpanda Cloud provides two complementary Model Context Protocol (MCP) options to help you build AI agents. diff --git a/modules/shared/images/ai-gateway.png b/modules/shared/partials/ai-gateway.png similarity index 100% rename from modules/shared/images/ai-gateway.png rename to modules/shared/partials/ai-gateway.png From c072cc04292d2006de4835710b763a3279d1f84e Mon Sep 17 00:00:00 2001 From: micheleRP Date: Tue, 6 Jan 2026 13:26:21 -0700 Subject: [PATCH 04/97] configure AI Gateway LLM & MCP endpoints in Claude Code & similar tools --- modules/ai-agents/pages/ai-gateway.adoc | 204 +++++++++++++++++++++++- 1 file changed, 197 insertions(+), 7 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway.adoc index 1e77e9709..5df70c215 100644 --- a/modules/ai-agents/pages/ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway.adoc @@ -6,6 +6,13 @@ NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and The Redpanda AI Gateway is a production-grade proxy that provides unified access to multiple Large Language Model (LLM) providers and Model Context Protocol (MCP) servers through a single endpoint. It maintains centralized control over routing, rate limiting, cost optimization, security, and observability. +Common gateway patterns: + +* *Team isolation*: Create separate gateways for each team to track usage and enforce budgets independently. +* *Environment separation*: Use different gateways for staging and production with appropriate rate limits. +* *Failover*: Configure a primary provider pool with a fallback pool for high availability. +* *A/B testing*: Distribute traffic across providers to compare performance and cost. + == Prerequisites * Access to the AI Gateway UI (provided by your administrator) @@ -20,7 +27,7 @@ Before a gateway owner can create a gateway, an administrator must enable LLM pr Providers represent upstream services (Anthropic, OpenAI) and associated credentials. Providers are disabled by default. An administrator must enable them explicitly by adding credentials. -. Navigate to *Providers*. +. In AI Gateways, navigate to *Providers*. . Select a provider (for example, Anthropic). . On the *Configuration* tab, enter your API Key. @@ -106,7 +113,7 @@ You can aggregate multiple MCP servers behind a single endpoint. For example: * The gateway presents a single aggregated MCP surface to the agent. * Agents can list/search tools and call them through the gateway. -*MCP orchestrator* +*MCP orchestrator:* The orchestrator is a built-in MCP server that enables programmatic tool calling. The agent can generate JavaScript to call multiple tools in a single orchestrated step, which reduces the number of round trips. For example, a workflow requiring 47 file reads can be reduced from 49 round trips to just 1. @@ -177,9 +184,192 @@ Guard for field existence: has(request.body.max_tokens) && request.body.max_tokens > 1000 ---- -== Common gateway patterns +== Integrate with AI agents and tools -* *Team isolation*: Create separate gateways for each team to track usage and enforce budgets independently. -* *Environment separation*: Use different gateways for staging and production with appropriate rate limits. -* *Failover*: Configure a primary provider pool with a fallback pool for high availability. -* *A/B testing*: Distribute traffic across providers to compare performance and cost. \ No newline at end of file +The AI Gateway provides standardized endpoints that work with various AI development tools and agents. This section shows how to configure popular tools to use your AI Gateway endpoints. + +=== MCP server endpoint + +If you've configured MCP tools in your gateway, AI agents can connect to the aggregated MCP endpoint: + +* MCP endpoint URL: `https://gw.ai.panda.com/mcp` + +* Headers required: +** `Authorization: Bearer your-api-key` +** `rp-aigw-id: your-gateway-id` + +This endpoint aggregates all MCP servers configured in your gateway, providing a unified interface for tool discovery and execution. + +=== Environment variables + +For consistent configuration across tools, set these environment variables: + +[source,bash] +---- +export REDPANDA_GATEWAY_URL="https://gw.ai.panda.com" +export REDPANDA_GATEWAY_ID="your-gateway-id" +export REDPANDA_API_KEY="your-api-key" +---- + +Many tools and SDKs can automatically use these environment variables when configured appropriately. + +=== Claude Code + +Configure Claude Code to use AI Gateway endpoints by creating or editing your MCP configuration file. + +*For Claude Desktop (with VS Code extension):* + +Create or edit `.vscode/settings.json`: + +[source,json] +---- +{ + "claude.mcpServers": { + "redpanda-ai-gateway": { + "command": "node", + "args": ["/path/to/mcp-redpanda-gateway/index.js"], + "env": { + "GATEWAY_ENDPOINT": "https://gw.ai.panda.com", + "GATEWAY_ID": "your-gateway-id", + "API_KEY": "your-api-key" + } + } + } +} +---- + +*For Claude Code CLI:* + +Create or edit `~/.claude/config.json`: + +[source,json] +---- +{ + "mcpServers": { + "redpanda-ai-gateway": { + "command": "npx", + "args": ["@redpanda/mcp-ai-gateway"], + "env": { + "REDPANDA_GATEWAY_URL": "https://gw.ai.panda.com", + "REDPANDA_GATEWAY_ID": "your-gateway-id", + "REDPANDA_API_KEY": "your-api-key" + } + } + }, + "apiProviders": { + "redpanda": { + "baseURL": "https://gw.ai.panda.com", + "headers": { + "rp-aigw-id": "your-gateway-id" + } + } + } +} +---- + +=== VS Code extensions + +Configure VS Code extensions that support OpenAI-compatible APIs: + +*Continue extension:* + +Edit your Continue config file (`~/.continue/config.json`): + +[source,json] +---- +{ + "models": [ + { + "title": "Redpanda AI Gateway - GPT-4", + "provider": "openai", + "model": "openai/gpt-4", + "apiBase": "https://gw.ai.panda.com", + "apiKey": "your-api-key", + "requestOptions": { + "headers": { + "rp-aigw-id": "your-gateway-id" + } + } + }, + { + "title": "Redpanda AI Gateway - Claude", + "provider": "anthropic", + "model": "anthropic/claude-3-5-sonnet-20241022", + "apiBase": "https://gw.ai.panda.com", + "apiKey": "your-api-key", + "requestOptions": { + "headers": { + "rp-aigw-id": "your-gateway-id" + } + } + } + ] +} +---- + +=== Cursor IDE + +Configure Cursor to route requests through the AI Gateway: + +. Open Cursor Settings (*Cursor* → *Settings* or `Cmd+,`) +. Navigate to *AI* settings +. Add a custom OpenAI-compatible provider: + +[source,json] +---- +{ + "cursor.ai.providers.openai.apiBase": "https://gw.ai.panda.com", + "cursor.ai.providers.openai.defaultHeaders": { + "rp-aigw-id": "your-gateway-id" + } +} +---- + +=== Custom applications + +For custom applications using OpenAI or Anthropic SDKs: + +*OpenAI SDK (Python):* + +[source,python] +---- +from openai import OpenAI + +client = OpenAI( + base_url="https://gw.ai.panda.com", + api_key="your-api-key", + default_headers={ + "rp-aigw-id": "your-gateway-id" + } +) +---- + +*Anthropic SDK (Python):* + +[source,python] +---- +from anthropic import Anthropic + +client = Anthropic( + base_url="https://gw.ai.panda.com", + api_key="your-api-key", + default_headers={ + "rp-aigw-id": "your-gateway-id" + } +) +---- + +*Node.js with OpenAI SDK:* + +[source,javascript] +---- +import OpenAI from 'openai'; + +const openai = new OpenAI({ + baseURL: 'https://gw.ai.panda.com', + apiKey: process.env.OPENAI_API_KEY, + defaultHeaders: { + 'rp-aigw-id': 'your-gateway-id' + } +}); +---- From 1588b36a216726945617c7f88978d162ede8d55e Mon Sep 17 00:00:00 2001 From: micheleRP Date: Tue, 6 Jan 2026 15:27:54 -0700 Subject: [PATCH 05/97] update Claude Code example + index page --- modules/ai-agents/pages/ai-gateway.adoc | 37 +++++++++---------------- modules/ai-agents/pages/index.adoc | 3 ++ 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway.adoc index 5df70c215..d82b4861c 100644 --- a/modules/ai-agents/pages/ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway.adoc @@ -215,30 +215,20 @@ Many tools and SDKs can automatically use these environment variables when confi === Claude Code -Configure Claude Code to use AI Gateway endpoints by creating or editing your MCP configuration file. +Configure Claude Code to use AI Gateway endpoints using HTTP transport for the MCP connection. -*For Claude Desktop (with VS Code extension):* +*For Claude Code CLI:* -Create or edit `.vscode/settings.json`: +Use the `claude mcp add` command to configure the HTTP transport: -[source,json] +[source,bash] ---- -{ - "claude.mcpServers": { - "redpanda-ai-gateway": { - "command": "node", - "args": ["/path/to/mcp-redpanda-gateway/index.js"], - "env": { - "GATEWAY_ENDPOINT": "https://gw.ai.panda.com", - "GATEWAY_ID": "your-gateway-id", - "API_KEY": "your-api-key" - } - } - } -} +claude mcp add --transport http redpanda-aigateway https://gw.ai.panda.com/mcp \ + --header "Authorization: Bearer YOUR_API_KEY" \ + --header "rp-aigw-id: GATEWAY_ID" ---- -*For Claude Code CLI:* +*Alternative configuration via config file:* Create or edit `~/.claude/config.json`: @@ -247,12 +237,11 @@ Create or edit `~/.claude/config.json`: { "mcpServers": { "redpanda-ai-gateway": { - "command": "npx", - "args": ["@redpanda/mcp-ai-gateway"], - "env": { - "REDPANDA_GATEWAY_URL": "https://gw.ai.panda.com", - "REDPANDA_GATEWAY_ID": "your-gateway-id", - "REDPANDA_API_KEY": "your-api-key" + "transport": "http", + "url": "https://gw.ai.panda.com/mcp", + "headers": { + "Authorization": "Bearer YOUR_API_KEY", + "rp-aigw-id": "GATEWAY_ID" } } }, diff --git a/modules/ai-agents/pages/index.adoc b/modules/ai-agents/pages/index.adoc index b5a5737db..0e31f53f1 100644 --- a/modules/ai-agents/pages/index.adoc +++ b/modules/ai-agents/pages/index.adoc @@ -2,3 +2,6 @@ :description: Learn about the Redpanda Agentic Data Plane, including the AI Gateway, AI agents, and MCP servers. :page-layout: index :page-aliases: develop:agents/about.adoc, develop:ai-agents/about.adoc + + +The Redpanda Agentic Data Plane platform provides AI agents with secure and governed access to enterprise data by acting as an intermediary layer between the data ecosystem and the agents themselves. It enables agents to safely discover, query, and act on data drawn from a wide range of sources while seamlessly combining real-time and historical context. Governance controls, access policies, and audit trails ensure that all interactions with data are traceable and compliant with organizational standards. From 9c757421202cbf5a342805f74dbd54aef17d3dc3 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 9 Jan 2026 13:31:40 -0700 Subject: [PATCH 06/97] revert nav title --- modules/ROOT/nav.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 500dccbda..2e268d196 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -69,7 +69,7 @@ ** xref:security:secrets.adoc[Secrets] ** xref:security:cloud-safety-reliability.adoc[Safety and Reliability] -* xref:ai-agents:index.adoc[Agentic Data Plane] +* xref:ai-agents:index.adoc[AI Agents] ** xref:ai-agents:ai-gateway.adoc[] ** xref:ai-agents:mcp/overview.adoc[MCP Overview] ** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] From fce266b3dc9e17765b56946b51279e3cf80a2183 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Sun, 11 Jan 2026 15:14:52 -0700 Subject: [PATCH 07/97] edits --- modules/ai-agents/pages/ai-gateway.adoc | 52 ++++++++----------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway.adoc index d82b4861c..2e327da98 100644 --- a/modules/ai-agents/pages/ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway.adoc @@ -4,7 +4,7 @@ NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. -The Redpanda AI Gateway is a production-grade proxy that provides unified access to multiple Large Language Model (LLM) providers and Model Context Protocol (MCP) servers through a single endpoint. It maintains centralized control over routing, rate limiting, cost optimization, security, and observability. +The Redpanda AI Gateway is a production-grade proxy that provides unified access to multiple Large Language Model (LLM) providers and Model Context Protocol (MCP) servers through a single endpoint. MCP servers expose tools that agents can discover and call. An AI Gateway maintains centralized control over routing, rate limiting, cost optimization, security, and observability. Common gateway patterns: @@ -21,7 +21,7 @@ Common gateway patterns: == Get started -Before a gateway owner can create a gateway, an administrator must enable LLM providers and models. +Before you can create a gateway, an administrator must enable LLM providers and models. === Step 1: Enable a provider @@ -29,11 +29,11 @@ Providers represent upstream services (Anthropic, OpenAI) and associated credent . In AI Gateways, navigate to *Providers*. . Select a provider (for example, Anthropic). -. On the *Configuration* tab, enter your API Key. +. On the *Configuration* tab for the provider, click *Add configuration* and enter your API Key. === Step 2: Enable models -The model catalog is the set of models made available through the gateway. Models are disabled by default. An administrator must enable them explicitly. +The model catalog is the set of models made available through the gateway. Models are disabled by default. After enabling a provider, an administrator can enable its models. The infrastructure that is serving the model is different based on the provider you select. For example, OpenAI has different reliability and availability metrics than Anthropic. When you consider all the metrics, you can design your gateway to use different providers for different use cases. @@ -81,13 +81,13 @@ TIP: A _workspace_ is conceptually similar to a _resource group_ in Redpanda str === Step 4: Configure LLM routing -On the Gateways page, select the *LLM* tab to configure rate limits, spend limits, and routing policies. +On the Gateways page, select the *LLM* tab to configure rate limits, spend limits, routing, and provider pools with fallback options. The LLM routing pipeline visually represents the request lifecycle: -. Rate Limit: For example, global rate limit of 100 requests/second -. Spend Limit / Monthly Budget: For example, $15K/month with blocking enforcement -. Routing to a primary provider pool with optional fallback provider pools: For example, primary route to Anthropic pool, fallback to OpenAI pool +. Rate Limit: For example, global rate limit of 100 requests/second. +. Spend Limit / Monthly Budget: For example, $15K/month with blocking enforcement, so it blocks requests after that budget is exceeded. +. Routing to a primary provider pool with optional fallback provider pools: For example, primary route to Anthropic backend pool, and if that fails, it will fallback to OpenAI pool. *Load balancing / multi-provider distribution:* If a provider pool contains multiple providers, you can distribute traffic (for example, balancing across Anthropic and OpenAI). @@ -96,39 +96,19 @@ TIP: Provider pool (UI) = Backend pool (API) === Step 5: Configure MCP tools -NOTE: Model Context Protocol (MCP) is a standard for connecting AI agents to external tools and data sources. MCP servers expose tools that agents can discover and call. +On the Gateways page, select the *MCP* tab to configure your MCP tool discovery and tool execution. This MCP proxy is an aggregator of MCP servers, allowing multiple MCP servers behind a single endpoint. Agents can then find tools and call them through the gateway. To configure the MCP proxy, add the following: -On the Gateways page, select the *MCP* tab to configure tool discovery and tool execution. +* Display name: When you drag a provider pool, you give it a name. +* Model dropdown: Choose a model from the available models in the catalog. +* Load Balancing options: If you have multiple providers, you can load balance requests between them; for example, round robin. -You can aggregate multiple MCP servers behind a single endpoint. For example: +MCP tools include a data catalog API, the memory store, a vector search service, and an MCP orchestrator. The *MCP orchestrator* is a built-in MCP server that enables programmatic tool calling. Agents can generate code to call multiple tools in a single orchestrated step, which reduces the number of round trips. For example, a workflow requiring 47 file reads can be reduced from 49 round trips to just 1. To add other tools, (for example, Slack), add the Slack MCP server endpoint. -* Data catalog API -* MCP orchestrator -* Research memory store -* Vector search service - -*How MCP works:* - -* You configure MCP server endpoints in the MCP gateway. -* The gateway presents a single aggregated MCP surface to the agent. -* Agents can list/search tools and call them through the gateway. - -*MCP orchestrator:* - -The orchestrator is a built-in MCP server that enables programmatic tool calling. The agent can generate JavaScript to call multiple tools in a single orchestrated step, which reduces the number of round trips. For example, a workflow requiring 47 file reads can be reduced from 49 round trips to just 1. +When many tools are aggregated, listing all tools can consume significant tokens. With *deferred tool loading*, instead of returning all tools, the MCP gateway initially returns a tool search capability and the MCP orchestrator. The agent then searches for the specific tool it needs and retrieves only that subset. That way, the exchange of messages between the MCP gateway and the agent is small. This can reduce token usage significantly when you have many tools configured. *REVIEWERS: When/how exactly do you use the orchestrator? Also what happens after they create a gateway? Please provide an example of how to validate end-to-end routing against the gateway endpoint!* -=== Step 6: Understand deferred tool loading (token savings) - -When many tools are aggregated, listing all tools can consume significant tokens. Deferred tool loading effectively behaves as lazy tool discovery: - -* Instead of returning all tools, the MCP gateway initially returns: -** a *tool search* capability, and -** the *MCP orchestrator* -* The agent then searches for the specific tool it needs and retrieves only that subset. - -This can reduce token usage significantly (for example, 80-90% depending on how many servers/tools are configured). +*REVIEWERS: How do users connect to the ADP catalog + MCP servers exposed through RPCN?* == Observability @@ -152,7 +132,7 @@ The AI Gateway uses Common Expression Language (CEL) for flexible routing and po * Apply different rate limits based on user tiers * Enforce policies based on request content -An inline editor in the UI helps you discover available request fields (headers, path, body, and so on). +The editor in the UI helps you discover available request fields (headers, path, body, and so on). === CEL examples From 85257c9ac1525e0be51d2dde03246d4c082523ac Mon Sep 17 00:00:00 2001 From: micheleRP Date: Sun, 11 Jan 2026 16:05:52 -0700 Subject: [PATCH 08/97] Add comprehensive AI Gateway documentation partials Added 7 new documentation files for AI Gateway: - what-is-ai-gateway.adoc: Overview, problem/solution framing, common patterns - quickstart-enhanced.adoc: Step-by-step quickstart with time markers - observability-logs.adoc: Request logs, filtering, and debugging - observability-metrics.adoc: Dashboards, analytics, and cost tracking - migration-guide.adoc: Safe migration from direct provider integration - cel-routing-cookbook.adoc: CEL routing patterns with examples - mcp-aggregation-guide.adoc: MCP aggregation and orchestration All files follow Redpanda documentation standards: - Sentence case headings - Imperative verbs for action headings - AsciiDoc format - Comprehensive placeholders for product-specific details Co-Authored-By: Claude Sonnet 4.5 --- .../partials/cel-routing-cookbook.adoc | 880 +++++++++++++++++ .../partials/mcp-aggregation-guide.adoc | 923 ++++++++++++++++++ .../ai-agents/partials/migration-guide.adoc | 866 ++++++++++++++++ .../partials/observability-logs.adoc | 631 ++++++++++++ .../partials/observability-metrics.adoc | 751 ++++++++++++++ .../partials/quickstart-enhanced.adoc | 503 ++++++++++ .../partials/what-is-ai-gateway.adoc | 419 ++++++++ 7 files changed, 4973 insertions(+) create mode 100644 modules/ai-agents/partials/cel-routing-cookbook.adoc create mode 100644 modules/ai-agents/partials/mcp-aggregation-guide.adoc create mode 100644 modules/ai-agents/partials/migration-guide.adoc create mode 100644 modules/ai-agents/partials/observability-logs.adoc create mode 100644 modules/ai-agents/partials/observability-metrics.adoc create mode 100644 modules/ai-agents/partials/quickstart-enhanced.adoc create mode 100644 modules/ai-agents/partials/what-is-ai-gateway.adoc diff --git a/modules/ai-agents/partials/cel-routing-cookbook.adoc b/modules/ai-agents/partials/cel-routing-cookbook.adoc new file mode 100644 index 000000000..44a505cfb --- /dev/null +++ b/modules/ai-agents/partials/cel-routing-cookbook.adoc @@ -0,0 +1,880 @@ += CEL routing: deep dive & cookbook + +== Overview + +Redpanda AI Gateway uses CEL (Common Expression Language) for dynamic request routing. CEL expressions evaluate request properties (headers, body, context) and determine which model or provider should handle each request. + +*CEL enables*: +* User-based routing (free vs premium tiers) +* Content-based routing (by prompt topic, length, complexity) +* Environment-based routing (staging vs production models) +* Cost controls (reject expensive requests in test environments) +* A/B testing (route percentage of traffic to new models) +* Geographic routing (by region header) +* Custom business logic (any condition you can express) + +== CEL basics + +=== What is cel? + +CEL (Common Expression Language) is a non-Turing-complete expression language designed for fast, safe evaluation. It's used by Google (Firebase, Cloud IAM), Kubernetes, Envoy, and other systems. + +*Key Properties*: +* *Safe*: Cannot loop infinitely or access system resources +* *Fast*: Evaluates in microseconds +* *Readable*: Similar to Python/JavaScript expressions +* *Type-safe*: Errors caught at configuration time, not runtime + +=== CEL syntax primer + +*Comparison Operators*: +[source,cel] +---- +== // equal +!= // Not equal +< // Less than +> // Greater than +<= // Less than or equal +>= // Greater than or equal +---- + + +*Logical Operators*: +[source,cel] +---- +&& // AND +|| // OR +! // NOT +---- + + +*Ternary Operator* (most common pattern): +[source,cel] +---- +condition ? value_if_true : value_if_false +---- + + +*Functions*: +[source,cel] +---- +.size() // Length of string or array +.contains("text") // String contains substring +.startsWith("x") // String starts with +.endsWith("x") // String ends with +.matches("regex") // Regex match +has(field) // Check if field exists +---- + + +*Examples*: +[source,cel] +---- +// Simple comparison +request.headers["tier"] == "premium" + +// Ternary (if-then-else) +request.headers["tier"] == "premium" ? "openai/gpt-4o" : "openai/gpt-4o-mini" + +// Logical AND +request.headers["tier"] == "premium" && request.headers["region"] == "us" + +// String contains +request.body.messages[0].content.contains("urgent") + +// Size check +request.body.messages.size() > 10 +---- + + +== Request object schema + +CEL expressions evaluate against the `request` object, which contains: + +// PLACEHOLDER: Confirm exact schema + +=== `request.headers` (map) + +All HTTP headers (lowercase keys). + +[source,cel] +---- +request.headers["x-user-tier"] // Custom header +request.headers["x-customer-id"] // Custom header +request.headers["user-agent"] // Standard header +request.headers["x-request-id"] // Standard header +---- + + +*Note*: Header names are case-insensitive in HTTP, but CEL requires lowercase keys. + +=== `request.body` (object) + +The JSON request body (for `/chat/completions`). + +[source,cel] +---- +request.body.model // String: Requested model +request.body.messages // Array: Conversation messages +request.body.messages[0].role // String: "system", "user", "assistant" +request.body.messages[0].content // String: Message content +request.body.messages.size() // Int: Number of messages +request.body.max_tokens // Int: Max completion tokens (if set) +request.body.temperature // Float: Temperature (if set) +request.body.stream // Bool: Streaming enabled (if set) +---- + + +*Note*: Fields are optional. Use `has()` to check existence: +[source,cel] +---- +has(request.body.max_tokens) ? request.body.max_tokens : 1000 +---- + + +=== `request.path` (string) + +The request path. + +[source,cel] +---- +request.path == "/v1/chat/completions" +request.path.startsWith("/v1/") +---- + + +=== `request.method` (string) + +The HTTP method. + +[source,cel] +---- +request.method == "POST" +---- + + +// PLACEHOLDER: Are there other fields? User context? Gateway context? Timestamp? + +== CEL routing patterns + +Each pattern follows this structure: +* *When to use*: Scenario description +* *Expression*: CEL code +* *What happens*: Routing behavior +* *Verify*: How to test +* *Cost/performance impact*: Implications + +''' + +=== Pattern 1: tier-based routing + +*When to use*: Different user tiers (free, pro, enterprise) should get different model quality + +*Expression*: +[source,cel] +---- +request.headers["x-user-tier"] == "enterprise" ? "openai/gpt-4o" : +request.headers["x-user-tier"] == "pro" ? "anthropic/claude-sonnet-3.5" : +"openai/gpt-4o-mini" +---- + + +*What happens*: +* Enterprise users → GPT-4o (best quality) +* Pro users → Claude Sonnet 3.5 (balanced) +* Free users → GPT-4o-mini (cost-effective) + +*Verify*: +[source,python] +---- +# Test enterprise +response = client.chat.completions.create( + model="auto", # PLACEHOLDER: How to trigger CEL routing? + messages=[{"role": "user", "content": "Test"}], + extra_headers={"x-user-tier": "enterprise"} +) +# Check logs: Should route to openai/gpt-4o + +# Test free +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Test"}], + extra_headers={"x-user-tier": "free"} +) +# Check logs: Should route to openai/gpt-4o-mini +---- + + +*Cost Impact*: +* Enterprise: ~$5.00 per 1K requests +* Pro: ~$3.50 per 1K requests +* Free: ~$0.50 per 1K requests + +*Use Case*: SaaS product with tiered pricing where model quality is a differentiator + +''' + +=== Pattern 2: environment-based routing + +*When to use*: Prevent staging from using expensive models + +*Expression*: +[source,cel] +---- +request.headers["x-environment"] == "production" + ? "openai/gpt-4o" + : "openai/gpt-4o-mini" +---- + + +*What happens*: +* Production → GPT-4o (best quality) +* Staging/dev → GPT-4o-mini (10x cheaper) + +*Verify*: +[source,python] +---- +# Set environment header +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Test"}], + extra_headers={"x-environment": "staging"} +) +# Check logs: Should route to gpt-4o-mini +---- + + +*Cost Impact*: +* Prevents staging from inflating costs +* Example: Staging with 100K test requests/day + * GPT-4o: $500/day ($15K/month) + * GPT-4o-mini: $50/day ($1.5K/month) + * *Savings: $13.5K/month* + +*Use Case*: Protect against runaway staging costs + +''' + +=== Pattern 3: content-length guard rails + +*When to use*: Block or downgrade long prompts to prevent cost spikes + +*Expression (Block)*: +[source,cel] +---- +request.body.messages.size() > 10 || request.body.max_tokens > 4000 + ? "reject" + : "openai/gpt-4o" +---- + + +*What happens*: +* Requests with >10 messages or >4000 max_tokens → Rejected with 400 error +* Normal requests → GPT-4o + +*Expression (Downgrade)*: +[source,cel] +---- +request.body.messages.size() > 10 || request.body.max_tokens > 4000 + ? "openai/gpt-4o-mini" // Cheaper model + : "openai/gpt-4o" // Normal model +---- + + +*What happens*: +* Long conversations → Downgraded to cheaper model +* Short conversations → Premium model + +*Verify*: +[source,python] +---- +# Test rejection +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": f"Message {i}"} for i in range(15)], + max_tokens=5000 +) +# Should return 400 error (rejected) + +# Test normal +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Short message"}], + max_tokens=100 +) +# Should route to gpt-4o +---- + + +*Cost Impact*: +* Prevents unexpected bills from verbose prompts +* Example: Block requests >10K tokens (would cost $0.15 each) + +*Use Case*: Staging cost controls, prevent prompt injection attacks that inflate token usage + +''' + +=== Pattern 4: topic-based routing + +*When to use*: Route different question types to specialized models + +*Expression*: +[source,cel] +---- +request.body.messages[0].content.contains("code") || +request.body.messages[0].content.contains("debug") || +request.body.messages[0].content.contains("programming") + ? "openai/gpt-4o" // Better at code + : "anthropic/claude-sonnet-3.5" // Better at general writing +---- + + +*What happens*: +* Coding questions → GPT-4o (optimized for code) +* General questions → Claude Sonnet (better prose) + +*Verify*: +[source,python] +---- +# Test code question +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Debug this Python code: ..."}] +) +# Check logs: Should route to gpt-4o + +# Test general question +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Write a blog post about AI"}] +) +# Check logs: Should route to claude-sonnet-3.5 +---- + + +*Cost Impact*: +* Optimize model selection for task type +* Could improve quality without increasing costs + +*Use Case*: Multi-purpose chatbot with both coding and general queries + +''' + +=== Pattern 5: geographic/regional routing + +*When to use*: Route by user region for compliance or latency optimization + +*Expression*: +[source,cel] +---- +request.headers["x-user-region"] == "eu" + ? "openai/gpt-4o-eu" // PLACEHOLDER: If regional models exist + : "openai/gpt-4o" +---- + + +*What happens*: +* EU users → EU-region model (GDPR compliance) +* Other users → Default region + +*Verify*: +[source,python] +---- +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Test"}], + extra_headers={"x-user-region": "eu"} +) +# Check logs: Should route to EU model +---- + + +*Cost Impact*: Neutral (same model, different region) + +*Use Case*: GDPR compliance, data residency requirements + +''' + +=== Pattern 6: customer-specific routing + +*When to use*: Different customers have different model access (enterprise features) + +*Expression*: +[source,cel] +---- +request.headers["x-customer-id"] == "customer_vip_123" + ? "anthropic/claude-opus-4" // Most expensive, best quality + : "anthropic/claude-sonnet-3.5" // Standard +---- + + +*What happens*: +* VIP customer → Best model +* Standard customers → Normal model + +*Verify*: +[source,python] +---- +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Test"}], + extra_headers={"x-customer-id": "customer_vip_123"} +) +# Check logs: Should route to claude-opus-4 +---- + + +*Cost Impact*: +* VIP: ~$7.50 per 1K requests +* Standard: ~$3.50 per 1K requests + +*Use Case*: Enterprise contracts with premium model access + +''' + +=== Pattern 7: a/b testing (percentage-based routing) + +*When to use*: Test new models with a percentage of traffic + +// PLACEHOLDER: Confirm if CEL can access random functions or if A/B testing requires different mechanism + +*Expression (if random is available)*: +[source,cel] +---- +// PLACEHOLDER: Verify CEL random function availability +random() < 0.10 + ? "anthropic/claude-opus-4" // 10% traffic to new model + : "openai/gpt-4o" // 90% traffic to existing model +---- + + +*Alternative (Hash-Based)*: +[source,cel] +---- +// Use customer ID hash for stable routing +hash(request.headers["x-customer-id"]) % 100 < 10 + ? "anthropic/claude-opus-4" + : "openai/gpt-4o" +---- + + +*What happens*: +* 10% of requests → New model (Opus 4) +* 90% of requests → Existing model (GPT-4o) + +*Verify*: +[source,python] +---- +# Send 100 requests, count which model was used +for i in range(100): + response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": f"Test {i}"}], + extra_headers={"x-customer-id": f"customer_{i}"} + ) +# Check logs: ~10 should use opus-4, ~90 should use gpt-4o +---- + + +*Cost Impact*: +* Allows safe, incremental rollout of new models +* Monitor quality/cost for new model before full adoption + +*Use Case*: Evaluate new models in production with real traffic + +''' + +=== Pattern 8: complexity-based routing + +*When to use*: Route simple queries to cheap models, complex queries to expensive models + +*Expression*: +[source,cel] +---- +request.body.messages.size() == 1 && +request.body.messages[0].content.size() < 100 + ? "openai/gpt-4o-mini" // Simple, short question + : "openai/gpt-4o" // Complex or long conversation +---- + + +*What happens*: +* Single short message (<100 chars) → Cheap model +* Multi-turn or long messages → Premium model + +*Verify*: +[source,python] +---- +# Test simple +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Hi"}] # 2 chars +) +# Check logs: Should route to gpt-4o-mini + +# Test complex +response = client.chat.completions.create( + model="auto", + messages=[ + {"role": "user", "content": "Long question here..." * 10}, + {"role": "assistant", "content": "Response"}, + {"role": "user", "content": "Follow-up"} + ] +) +# Check logs: Should route to gpt-4o +---- + + +*Cost Impact*: +* Can reduce costs significantly if simple queries are common +* Example: 50% of queries are simple, save 90% on those = 45% total savings + +*Use Case*: FAQ chatbot with mix of simple lookups and complex questions + +''' + +=== Pattern 9: time-based routing + +*When to use*: Use cheaper models during off-peak hours + +// PLACEHOLDER: Confirm if CEL has access to current timestamp + +*Expression (if time functions available)*: +[source,cel] +---- +// PLACEHOLDER: Verify CEL time function availability +now().hour >= 22 || now().hour < 6 // 10pm - 6am + ? "openai/gpt-4o-mini" // Off-peak: cheaper model + : "openai/gpt-4o" // Peak hours: best model +---- + + +*What happens*: +* Off-peak hours (10pm-6am) → Cheap model +* Peak hours (6am-10pm) → Premium model + +*Cost Impact*: +* Optimize for user experience during peak usage +* Save costs during low-traffic hours + +*Use Case*: Consumer apps with time-zone-specific usage patterns + +''' + +=== Pattern 10: fallback chain (multi-level) + +*When to use*: Complex fallback logic beyond simple primary/secondary + +*Expression*: +[source,cel] +---- +request.headers["x-priority"] == "critical" + ? "openai/gpt-4o" // First choice for critical + : request.headers["x-user-tier"] == "premium" + ? "anthropic/claude-sonnet-3.5" // Second choice for premium + : "openai/gpt-4o-mini" // Default for everyone else +---- + + +*What happens*: +* Critical requests → Always GPT-4o +* Premium non-critical → Claude Sonnet +* Everyone else → GPT-4o-mini + +*Verify*: Test with different header combinations + +*Cost Impact*: Ensures SLA for critical requests while optimizing costs elsewhere + +*Use Case*: Production systems with SLA requirements + +''' + +== Advanced CEL patterns + +=== Pattern: default values with `has()` + +*Problem*: Field might not exist in request + +*Expression*: +[source,cel] +---- +has(request.body.max_tokens) && request.body.max_tokens > 2000 + ? "openai/gpt-4o" // Long response expected + : "openai/gpt-4o-mini" // Short response +---- + + +*What happens*: Safely checks if `max_tokens` exists before comparing + +=== Pattern: multiple conditions with parentheses + +*Expression*: +[source,cel] +---- +(request.headers["x-user-tier"] == "premium" || + request.headers["x-customer-id"] == "vip_123") && +request.headers["x-environment"] == "production" + ? "openai/gpt-4o" + : "openai/gpt-4o-mini" +---- + + +*What happens*: Premium users OR VIP customer, AND production → GPT-4o + +=== Pattern: regex matching + +*Expression*: +[source,cel] +---- +request.body.messages[0].content.matches("(?i)(urgent|asap|emergency)") + ? "openai/gpt-4o" // Route urgent requests to best model + : "openai/gpt-4o-mini" +---- + + +*What happens*: Messages containing "urgent", "ASAP", or "emergency" (case-insensitive) → GPT-4o + +=== Pattern: string array contains + +*Expression*: +[source,cel] +---- +["customer_1", "customer_2", "customer_3"].exists(c, c == request.headers["x-customer-id"]) + ? "openai/gpt-4o" // Whitelist of customers + : "openai/gpt-4o-mini" +---- + + +*What happens*: Only specific customers get premium model + +=== Pattern: reject invalid requests + +*Expression*: +[source,cel] +---- +!has(request.body.messages) || request.body.messages.size() == 0 + ? "reject" // PLACEHOLDER: Confirm "reject" is supported + : "openai/gpt-4o" +---- + + +*What happens*: Requests without messages are rejected (400 error) + +== Test CEL expressions + +=== Option 1: CEL editor in UI (if available) + +// PLACEHOLDER: Add screenshot if UI has CEL editor with test mode + +1. Navigate to Gateway → Routing Rules +2. Enter CEL expression +3. Click "Test" +4. Input test headers/body +5. View evaluated result + +=== Option 2: send test requests + +[source,python] +---- +def test_cel_routing(headers, messages): + """Test CEL routing with specific headers and messages""" + response = client.chat.completions.create( + model="auto", # PLACEHOLDER: Confirm trigger for CEL routing + messages=messages, + extra_headers=headers, + max_tokens=10 # Keep it cheap + ) + + # Check logs to see which model was used + print(f"Headers: {headers}") + print(f"Routed to: {response.model}") # PLACEHOLDER: Does response include actual model? + +# Test tier-based routing +test_cel_routing( + {"x-user-tier": "premium"}, + [{"role": "user", "content": "Test"}] +) +test_cel_routing( + {"x-user-tier": "free"}, + [{"role": "user", "content": "Test"}] +) +---- + + +=== Option 3: cli test (if available) + +[source,bash] +---- +# PLACEHOLDER: If CLI tool exists for testing CEL +rpk cloud ai-gateway test-cel \ + --gateway-id gw_abc123 \ + --expression 'request.headers["tier"] == "premium" ? "openai/gpt-4o" : "openai/gpt-4o-mini"' \ + --header 'tier: premium' \ + --body '{"messages": [{"role": "user", "content": "Test"}]}' + +# Expected output: openai/gpt-4o +---- + + +== Common CEL errors + +=== Error: "unknown field" + +*Symptom*: +[source,text] +---- +Error: Unknown field 'request.headers.x-user-tier' +---- + + +*Cause*: Wrong syntax (dot notation instead of bracket notation for headers) + +*Fix*: +[source,cel] +---- +// Wrong +request.headers.x-user-tier + +// Correct +request.headers["x-user-tier"] +---- + + +=== Error: "type mismatch" + +*Symptom*: +[source,text] +---- +Error: Type mismatch: expected bool, got string +---- + + +*Cause*: Forgot comparison operator + +*Fix*: +[source,cel] +---- +// Wrong (returns string) +request.headers["tier"] + +// Correct (returns bool) +request.headers["tier"] == "premium" +---- + + +=== Error: "field does not exist" + +*Symptom*: +[source,text] +---- +Error: No such key: max_tokens +---- + + +*Cause*: Accessing field that doesn't exist in request + +*Fix*: +[source,cel] +---- +// Wrong (crashes if max_tokens not in request) +request.body.max_tokens > 1000 + +// Correct (checks existence first) +has(request.body.max_tokens) && request.body.max_tokens > 1000 +---- + + +=== Error: "index out of bounds" + +*Symptom*: +[source,text] +---- +Error: Index 0 out of bounds for array of size 0 +---- + + +*Cause*: Accessing array element that doesn't exist + +*Fix*: +[source,cel] +---- +// Wrong (crashes if messages empty) +request.body.messages[0].content.contains("test") + +// Correct (checks size first) +request.body.messages.size() > 0 && request.body.messages[0].content.contains("test") +---- + + +== CEL performance considerations + +=== Expression complexity + +*Fast* (<1ms evaluation): +[source,cel] +---- +request.headers["tier"] == "premium" ? "openai/gpt-4o" : "openai/gpt-4o-mini" +---- + + +*Slower* (~5-10ms evaluation): +[source,cel] +---- +request.body.messages[0].content.matches("complex.*regex.*pattern") +---- + + +*Recommendation*: Keep expressions simple. Complex regex can add latency. + +=== Number of evaluations + +Each request evaluates CEL expression once. Total latency impact: +* Simple expression: <1ms +* Complex expression: ~5-10ms + +*Acceptable for most use cases.* + +== CEL function reference + +// PLACEHOLDER: Comprehensive list of available CEL functions in AI Gateway + +=== String functions + +| Function | Description | Example | +|----------|-------------|---------| +| `size()` | String length | `"hello".size() == 5` | +| `contains(s)` | String contains | `"hello".contains("ell")` | +| `startsWith(s)` | String starts with | `"hello".startsWith("he")` | +| `endsWith(s)` | String ends with | `"hello".endsWith("lo")` | +| `matches(regex)` | Regex match | `"hello".matches("h.*o")` | + +=== Array functions + +| Function | Description | Example | +|----------|-------------|---------| +| `size()` | Array length | `[1,2,3].size() == 3` | +| `exists(x, cond)` | Any element matches | `[1,2,3].exists(x, x > 2)` | +| `all(x, cond)` | All elements match | `[1,2,3].all(x, x > 0)` | + +=== Utility functions + +| Function | Description | Example | +|----------|-------------|---------| +| `has(field)` | Field exists | `has(request.body.max_tokens)` | + +// PLACEHOLDER: Other functions like hash(), random(), now()? + +== Next steps + +* *Apply CEL Routing* → [Gateway Configuration Guide](// PLACEHOLDER: link) +* *Test Routing* → [End-to-End Validation](// PLACEHOLDER: link) +* *Monitor Routing Decisions* → [Observability: Logs](// PLACEHOLDER: link) +* *Optimize Costs* → [Cost Optimization Guide](// PLACEHOLDER: link) +* *Multi-Tenancy Patterns* → [Multi-Tenancy Guide](// PLACEHOLDER: link) + +== Related pages + +* [Quickstart](// PLACEHOLDER: link) +* [Provider Pools & Fallback](// PLACEHOLDER: link) +* [Rate Limiting](// PLACEHOLDER: link) +* [Observability](// PLACEHOLDER: link) diff --git a/modules/ai-agents/partials/mcp-aggregation-guide.adoc b/modules/ai-agents/partials/mcp-aggregation-guide.adoc new file mode 100644 index 000000000..c09684357 --- /dev/null +++ b/modules/ai-agents/partials/mcp-aggregation-guide.adoc @@ -0,0 +1,923 @@ += MCP aggregation & orchestration guide + +== Overview + +AI Gateway provides MCP (Model Context Protocol) aggregation, allowing AI agents to access tools from multiple MCP servers through a single unified endpoint. This eliminates the need for agents to manage multiple MCP connections and significantly reduces token costs through deferred tool loading. + +*MCP Aggregation Benefits*: +* *Single Endpoint*: One MCP endpoint aggregates all approved MCP servers +* *Token Reduction*: 80-90% fewer tokens through deferred tool loading +* *Centralized Governance*: Admin-approved MCP servers only +* *Orchestration*: JavaScript-based orchestrator reduces multi-step round trips +* *Security*: Controlled tool execution environment + +== What is mcp? + +*Model Context Protocol (MCP)* is a standard for exposing tools (functions) that AI agents can discover and invoke. MCP servers provide tools like: +* Database queries +* File system operations +* API integrations (CRM, payment, analytics) +* Search (web, vector, enterprise) +* Code execution +* Workflow automation + +*Without AI Gateway*: +* Agent connects to each MCP server individually +* Agent loads ALL tools from ALL servers upfront (high token cost) +* No centralized governance or security +* Complex configuration + +*With AI Gateway*: +* Agent connects to gateway's unified `/mcp` endpoint +* Gateway aggregates tools from approved MCP servers +* Deferred loading: Only search + orchestrator tools sent initially +* Agent queries for specific tools when needed (token savings) +* Centralized governance and observability + +== Architecture + +[source,text] +---- +┌─────────────────┐ +│ AI Agent │ +│ (Claude, GPT) │ +└────────┬────────┘ + │ + │ 1. Discover tools via /mcp endpoint + │ 2. Invoke specific tool + │ +┌────────▼────────────────────────────────┐ +│ AI Gateway (MCP Aggregator) │ +│ │ +│ ┌─────────────────────────────────┐ │ +│ │ Deferred Tool Loading │ │ +│ │ (Send search + orchestrator │ │ +│ │ initially, defer others) │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────┐ │ +│ │ Orchestrator (JavaScript) │ │ +│ │ (Reduce round trips for │ │ +│ │ multi-step workflows) │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────┐ │ +│ │ Approved MCP Server Registry │ │ +│ │ (Admin-controlled) │ │ +│ └─────────────────────────────────┘ │ +└────────┬────────────────────────────────┘ + │ + │ Routes to appropriate MCP server + │ + ┌────▼─────┬──────────┬─────────┐ + │ │ │ │ +┌───▼────┐ ┌──▼─────┐ ┌──▼──────┐ ┌▼──────┐ +│ MCP │ │ MCP │ │ MCP │ │ MCP │ +│Database│ │Filesystem│ │ Slack │ │Search │ +│Server │ │ Server │ │ Server │ │Server │ +└────────┘ └────────┘ └─────────┘ └───────┘ +---- + + +== MCP request lifecycle + +=== 1. tool discovery (initial connection) + +*Agent Request*: +[source,http] +---- +GET /mcp/tools +Headers: + Authorization: Bearer {TOKEN} + rp-aigw-id: {GATEWAY_ID} + rp-aigw-mcp-deferred: true # Enable deferred loading +---- + + +*Gateway Response* (with deferred loading): +[source,json] +---- +{ + "tools": [ + { + "name": "search_tools", + "description": "Query available tools by keyword or category", + "input_schema": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "category": {"type": "string"} + } + } + }, + { + "name": "orchestrator", + "description": "Execute multi-step workflows with JavaScript logic", + "input_schema": { + "type": "object", + "properties": { + "workflow": {"type": "string"}, + "context": {"type": "object"} + } + } + } + ] +} +---- + + +*Note*: Only 2 tools returned initially (search + orchestrator), not all 50+ tools from all MCP servers. + +*Token Savings*: +* Without deferred loading: ~5,000-10,000 tokens (all tool definitions) +* With deferred loading: ~500-1,000 tokens (2 tool definitions) +* *80-90% reduction* + +=== 2. tool query (when agent needs specific tool) + +*Agent Request*: +[source,http] +---- +POST /mcp/tools/search_tools +Headers: + Authorization: Bearer {TOKEN} + rp-aigw-id: {GATEWAY_ID} +Body: +{ + "query": "database query" +} +---- + + +*Gateway Response*: +[source,json] +---- +{ + "tools": [ + { + "name": "execute_sql", + "description": "Execute SQL query against the database", + "mcp_server": "database-server", + "input_schema": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "database": {"type": "string"} + }, + "required": ["query"] + } + }, + { + "name": "list_tables", + "description": "List all tables in the database", + "mcp_server": "database-server", + "input_schema": { + "type": "object", + "properties": { + "database": {"type": "string"} + } + } + } + ] +} +---- + + +*Agent receives only relevant tools* based on query. + +=== 3. tool execution + +*Agent Request*: +[source,http] +---- +POST /mcp/tools/execute_sql +Headers: + Authorization: Bearer {TOKEN} + rp-aigw-id: {GATEWAY_ID} +Body: +{ + "query": "SELECT * FROM users WHERE tier = 'premium' LIMIT 10", + "database": "prod" +} +---- + + +*Gateway*: +1. Routes to appropriate MCP server (database-server) +2. Executes tool +3. Returns result + +*Gateway Response*: +[source,json] +---- +{ + "result": [ + {"id": 1, "name": "Alice", "tier": "premium"}, + {"id": 2, "name": "Bob", "tier": "premium"}, + ... + ] +} +---- + + +*Agent receives result* and can continue reasoning. + +== Deferred tool loading: deep dive + +=== How it works + +*Traditional MCP (No Deferred Loading)*: +1. Agent connects to MCP endpoint +2. Gateway sends ALL tools from ALL MCP servers (50+ tools) +3. Agent includes ALL tool definitions in EVERY LLM request +4. High token cost: ~5,000-10,000 tokens per request + +*Deferred Loading (AI Gateway)*: +1. Agent connects to MCP endpoint with `rp-aigw-mcp-deferred: true` header +2. Gateway sends only 2 tools: `search_tools` + `orchestrator` +3. Agent includes only 2 tool definitions in LLM request (~500-1,000 tokens) +4. When agent needs specific tool: + * Agent calls `search_tools` with query (e.g., "database") + * Gateway returns matching tools + * Agent calls specific tool (e.g., `execute_sql`) +5. Total token cost: Initial 500-1,000 + per-query ~200-500 + * *Still 80-90% lower than loading all tools* + +=== When to use deferred loading + +*Use Deferred Loading When*: +* You have 10+ tools across multiple MCP servers +* Agents don't need all tools for every request +* Token costs are a concern +* Agents can handle multi-step workflows (search → execute) + +*Don't Use Deferred Loading When*: +* You have <5 tools total (overhead not worth it) +* Agents need all tools for every request (rare) +* Latency is more important than token costs (deferred adds 1 round trip) + +=== Configure deferred loading + +// PLACEHOLDER: Add UI path or configuration method + +*Option 1: Enable at Gateway Level* (recommended) +[source,yaml] +---- +# PLACEHOLDER: Actual configuration format +mcp: + deferred_loading: true # Default for all agents using this gateway +---- + + +*Option 2: Enable Per-Request* (agent-controlled) +[source,python] +---- +# Agent includes header +headers = { + "rp-aigw-id": "gw_abc123", + "rp-aigw-mcp-deferred": "true" # Enable for this request +} +---- + + +=== Measure token savings + +*Compare token usage before/after deferred loading*: + +1. *Check Logs Without Deferred Loading*: + * Filter: Gateway = your-gateway, Model = your-model, Date = before enabling + * Average tokens per request: // PLACEHOLDER: measure + +2. *Enable Deferred Loading* + +3. *Check Logs After Deferred Loading*: + * Filter: Same gateway/model, Date = after enabling + * Average tokens per request: // PLACEHOLDER: measure + +4. *Calculate Savings*: + ``` + Savings % = ((Before - After) / Before) × 100 + ``` + +*Expected Results*: 80-90% reduction in average tokens per request + +== Orchestrator: multi-step workflows + +=== What is the orchestrator? + +The *orchestrator* is a special tool that executes JavaScript workflows, reducing multi-step interactions from multiple round trips to a single request. + +*Without Orchestrator*: +1. Agent: "Search vector database for relevant docs" → Round trip 1 +2. Agent receives results, evaluates: "Results insufficient" +3. Agent: "Fallback to web search" → Round trip 2 +4. Agent receives results, processes → Round trip 3 +5. *Total: 3 round trips* (high latency, 3× token cost) + +*With Orchestrator*: +1. Agent: "Execute workflow: Search vector DB → if insufficient, fallback to web search" +2. Gateway executes entire workflow in JavaScript +3. Agent receives final result → *1 round trip* + +*Benefits*: +* *Latency Reduction*: 1 round trip vs 3+ +* *Token Reduction*: No intermediate LLM calls needed +* *Reliability*: Workflow logic executes deterministically +* *Cost*: Single LLM call instead of multiple + +=== When to use orchestrator + +*Use Orchestrator When*: +* Multi-step workflows with conditional logic (if/else) +* Fallback patterns (try A, if fails, try B) +* Sequential tool calls with dependencies +* Loop-based operations (iterate, aggregate) + +*Don't Use Orchestrator When*: +* Single tool call (no benefit) +* Agent needs to reason between steps (orchestrator is deterministic) +* Workflow requires LLM judgment at each step + +=== Orchestrator example: search with fallback + +*Scenario*: Search vector database; if results insufficient, fallback to web search. + +*Without Orchestrator* (3 round trips): +[source,python] +---- +# Agent's internal reasoning (3 separate LLM calls) + +# Round trip 1: Search vector DB +vector_results = call_tool("vector_search", {"query": "Redpanda pricing"}) + +# Round trip 2: Agent evaluates results +if len(vector_results) < 3: + # Round trip 3: Fallback to web search + web_results = call_tool("web_search", {"query": "Redpanda pricing"}) + results = web_results +else: + results = vector_results + +# Agent processes final results +---- + + +*With Orchestrator* (1 round trip): +[source,python] +---- +# Agent invokes orchestrator once +results = call_tool("orchestrator", { + "workflow": """ + // JavaScript workflow + const vectorResults = await tools.vector_search({ + query: context.query + }); + + if (vectorResults.length < 3) { + // Fallback to web search + const webResults = await tools.web_search({ + query: context.query + }); + return webResults; + } + + return vectorResults; + """, + "context": { + "query": "Redpanda pricing" + } +}) + +# Agent receives final results directly +---- + + +*Savings*: +* *Latency*: ~3-5 seconds (3 round trips) → ~1-2 seconds (1 round trip) +* *Tokens*: ~1,500 tokens (3 LLM calls) → ~500 tokens (1 LLM call) +* *Cost*: ~$0.0075 → ~$0.0025 (67% reduction) + +=== Orchestrator API + +// PLACEHOLDER: Confirm orchestrator API details + +*Tool Name*: `orchestrator` + +*Input Schema*: +[source,json] +---- +{ + "workflow": "string (JavaScript code)", + "context": "object (variables available to workflow)" +} +---- + + +*Available in Workflow*: +* `tools.{tool_name}(params)`: Call any tool from approved MCP servers +* `context.{variable}`: Access context variables +* Standard JavaScript: `if`, `for`, `while`, `try/catch`, `async/await` + +*Security*: +* Sandboxed execution (no file system, network, or system access) +* Timeout: // PLACEHOLDER: e.g., 30 seconds +* Memory limit: // PLACEHOLDER: e.g., 128MB + +*Limitations*: +* Cannot call external APIs directly (must use MCP tools) +* Cannot import npm packages (built-in JS only) +* // PLACEHOLDER: Other limitations? + +=== Orchestrator example: data aggregation + +*Scenario*: Fetch user data from database, calculate summary statistics. + +[source,python] +---- +results = call_tool("orchestrator", { + "workflow": """ + // Fetch all premium users + const users = await tools.execute_sql({ + query: "SELECT * FROM users WHERE tier = 'premium'", + database: "prod" + }); + + // Calculate statistics + const stats = { + total: users.length, + by_region: {}, + avg_spend: 0 + }; + + let totalSpend = 0; + for (const user of users) { + // Count by region + if (!stats.by_region[user.region]) { + stats.by_region[user.region] = 0; + } + stats.by_region[user.region]++; + + // Sum spend + totalSpend += user.monthly_spend; + } + + stats.avg_spend = totalSpend / users.length; + + return stats; + """, + "context": {} +}) +---- + + +*Output*: +[source,json] +---- +{ + "total": 1250, + "by_region": { + "us-east": 600, + "us-west": 400, + "eu": 250 + }, + "avg_spend": 149.50 +} +---- + + +*vs Without Orchestrator*: +* Would require fetching all users to agent → agent processes → 2 round trips +* Orchestrator: All processing in gateway → 1 round trip + +=== Orchestrator best practices + +*DO*: +* Use for deterministic workflows (same input → same output) +* Use for sequential operations with dependencies +* Use for fallback patterns +* Handle errors with `try/catch` +* Keep workflows readable (add comments) + +*DON'T*: +* Use for workflows requiring LLM reasoning at each step (let agent handle that) +* Execute long-running operations (timeout will hit) +* Access external resources (use MCP tools instead) +* Execute untrusted user input (security risk) + +== MCP server administration + +=== Add MCP servers + +// PLACEHOLDER: Add UI path for MCP server management + +*Prerequisites*: +* MCP server URL +* Authentication method (if required) +* List of tools to enable + +*Steps*: +1. *Navigate to MCP Servers*: + * Console → AI Gateway → MCP Servers → Add Server + +2. *Configure Server*: + ```yaml + # PLACEHOLDER: Actual configuration format + name: database-server + url: https://mcp-database.example.com + authentication: + type: bearer_token + token: ${SECRET_REF} # Reference to secret + enabled_tools: + * execute_sql + * list_tables + * describe_table + ``` + +3. *Test Connection*: + * Gateway attempts connection to MCP server + * Verifies authentication + * Retrieves tool list + +4. *Enable Server*: + * Server status: Active + * Tools available to agents + +*Common MCP Servers*: +* *Database*: PostgreSQL, MySQL, MongoDB query tools +* *Filesystem*: Read/write/search files +* *API Integrations*: Slack, GitHub, Salesforce, Stripe +* *Search*: Web search, vector search, enterprise search +* *Code Execution*: Python, JavaScript sandboxes +* *Workflow*: Zapier, n8n integrations + +=== MCP server approval workflow + +*Why Approval is Required*: +* Security: Prevent agents from accessing unauthorized systems +* Governance: Control which tools are available +* Cost: Some tools are expensive (API calls, compute) +* Compliance: Audit trail of approved tools + +*Approval Process*: +// PLACEHOLDER: Confirm if there's an approval workflow or if admins directly enable servers + +1. *Request*: User/team requests MCP server +2. *Review*: Admin reviews security, cost, necessity +3. *Approval/Rejection*: Admin decision +4. *Configuration*: If approved, admin adds server to gateway + +*Rejected Server Behavior*: +* Server not listed in tool discovery +* Agent cannot query or invoke tools from this server +* Requests return `403 Forbidden` + +=== Restrict MCP server access + +*Per-Gateway Restrictions*: +[source,yaml] +---- +# PLACEHOLDER: Actual configuration format +gateways: + - name: production-gateway + mcp_servers: + allowed: + - database-server # Only this server allowed + denied: + - filesystem-server # Explicitly denied + + - name: staging-gateway + mcp_servers: + allowed: + - "*" # All approved servers allowed +---- + + +*Use Cases*: +* Production gateway: Only production-safe tools +* Staging gateway: All tools for testing +* Customer-specific gateway: Only tools relevant to customer + +=== MCP server versioning + +// PLACEHOLDER: How is MCP server versioning handled? + +*Challenge*: MCP server updates may change tool schemas + +*Recommendations*: +1. *Pin Versions* (if supported): + ```yaml + mcp_servers: + * name: database-server + version: "1.2.3" # Pin to specific version + ``` + +2. *Test in Staging First*: + * Update MCP server in staging gateway + * Test agent workflows + * Promote to production when validated + +3. *Monitor Breaking Changes*: + * Subscribe to MCP server changelogs + * Set up alerts for schema changes + +== MCP observability + +=== Logs + +MCP tool invocations appear in request logs with: +* Tool name +* MCP server +* Input parameters +* Output result +* Execution time +* Errors (if any) + +*Filter Logs by MCP*: +[source,text] +---- +Filter: request.path.startsWith("/mcp") +---- + + +*Common Log Fields*: +| Field | Description | Example | +|-------|-------------|---------| +| Tool | Tool invoked | `execute_sql` | +| MCP Server | Which server handled it | `database-server` | +| Input | Parameters sent | `{"query": "SELECT ..."}` | +| Output | Result returned | `[{"id": 1, ...}]` | +| Latency | Tool execution time | `250ms` | +| Status | Success/failure | `200`, `500` | + +=== Metrics + +// PLACEHOLDER: Confirm if MCP-specific metrics exist + +*MCP-Specific Metrics* (if available): +* MCP requests per second +* Tool invocation count (by tool, by MCP server) +* MCP latency (p50, p95, p99) +* MCP error rate (by server, by tool) +* Orchestrator execution count +* Orchestrator execution time + +*Dashboard*: MCP Analytics +* Top tools by usage +* Top MCP servers by latency +* Error rate by MCP server +* Token savings from deferred loading + +=== Debug MCP issues + +*Issue: "Tool not found"* + +*Possible Causes*: +1. MCP server not added to gateway +2. Tool not enabled in MCP server configuration +3. Deferred loading enabled but agent didn't query for tool first + +*Solution*: +1. Verify MCP server is active: // PLACEHOLDER: UI path +2. Verify tool is in enabled_tools list +3. If deferred loading: Agent must call `search_tools` first + +*Issue: "MCP server timeout"* + +*Possible Causes*: +1. MCP server is down/unreachable +2. Tool execution is slow (e.g., expensive database query) +3. Gateway timeout too short + +*Solution*: +1. Check MCP server health +2. Optimize tool (e.g., add database index) +3. Increase timeout: // PLACEHOLDER: How to configure? + +*Issue: "Orchestrator workflow failed"* + +*Possible Causes*: +1. JavaScript syntax error +2. Tool invocation failed inside workflow +3. Timeout exceeded +4. Memory limit exceeded + +*Solution*: +1. Test workflow syntax in JavaScript playground +2. Check logs for tool error inside orchestrator +3. Simplify workflow or increase timeout +4. Reduce data processing in workflow + +== Security considerations + +=== Tool execution sandboxing + +// PLACEHOLDER: Confirm sandboxing implementation + +*Orchestrator Sandbox*: +* No file system access +* No network access (except via MCP tools) +* No system calls +* Memory limit: // PLACEHOLDER: e.g., 128MB +* Execution timeout: // PLACEHOLDER: e.g., 30s + +*MCP Tool Execution*: +* Tools execute in MCP server's environment (not gateway) +* Gateway does not execute tool code (only proxies requests) +* Security is MCP server's responsibility + +=== Authentication + +*Gateway → MCP Server*: +* Bearer token (most common) +* API key +* mTLS (for high-security environments) + +*Agent → Gateway*: +* Standard gateway authentication (Redpanda Cloud token) +* `rp-aigw-id` header identifies gateway (and its approved MCP servers) + +=== Audit trail + +All MCP operations logged: +* Who (agent/user) invoked tool +* When (timestamp) +* What tool was invoked +* What parameters were sent +* What result was returned +* Whether it succeeded or failed + +*Use Case*: Compliance, security investigation, debugging + +=== Restrict dangerous tools + +*Recommendation*: Don't enable destructive tools in production gateways + +*Examples of Dangerous Tools*: +* File deletion (`delete_file`) +* Database writes without safeguards (`execute_sql` with UPDATE/DELETE) +* Payment operations (`charge_customer`) +* System commands (`execute_bash`) + +*Best Practice*: +* Read-only tools in production gateway +* Write tools only in staging gateway (with approval workflows) +* Wrap dangerous operations in MCP server with safeguards (e.g., "require confirmation token") + +== MCP + LLM routing + +=== Combine MCP with CEL routing + +*Use Case*: Route agents to different MCP servers based on customer tier + +*CEL Expression*: +[source,cel] +---- +request.headers["x-customer-tier"] == "enterprise" + ? "gateway-with-premium-mcp-servers" + : "gateway-with-basic-mcp-servers" +---- + + +*Result*: +* Enterprise customers: Access to proprietary data, expensive APIs +* Basic customers: Access to public data, free APIs + +=== MCP with provider pools + +*Scenario*: Different agents use different models + different tools + +*Configuration*: +* Gateway A: GPT-4o + database + CRM MCP servers +* Gateway B: Claude Sonnet + web search + analytics MCP servers + +*Use Case*: Optimize model-tool pairing (some models better at certain tools) + +== Integration examples + +=== Python (openai sdk) + +[source,python] +---- +from openai import OpenAI + +# Initialize client with MCP endpoint +client = OpenAI( + base_url="https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1", + api_key=os.getenv("REDPANDA_CLOUD_TOKEN"), + default_headers={ + "rp-aigw-id": os.getenv("GATEWAY_ID"), + "rp-aigw-mcp-deferred": "true" # Enable deferred loading + } +) + +# Discover tools +tools_response = requests.get( + "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/mcp/tools", + headers={ + "Authorization": f"Bearer {os.getenv('REDPANDA_CLOUD_TOKEN')}", + "rp-aigw-id": os.getenv("GATEWAY_ID"), + "rp-aigw-mcp-deferred": "true" + } +) +tools = tools_response.json()["tools"] + +# Agent uses tools +response = client.chat.completions.create( + model="anthropic/claude-sonnet-3.5", + messages=[ + {"role": "user", "content": "Query the database for premium users"} + ], + tools=tools, # Pass MCP tools to agent + tool_choice="auto" +) + +# Handle tool calls +if response.choices[0].message.tool_calls: + for tool_call in response.choices[0].message.tool_calls: + # Execute tool via gateway + tool_result = requests.post( + f"https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/mcp/tools/{tool_call.function.name}", + headers={ + "Authorization": f"Bearer {os.getenv('REDPANDA_CLOUD_TOKEN')}", + "rp-aigw-id": os.getenv("GATEWAY_ID") + }, + json=json.loads(tool_call.function.arguments) + ) + + # Continue conversation with tool result + response = client.chat.completions.create( + model="anthropic/claude-sonnet-3.5", + messages=[ + {"role": "user", "content": "Query the database for premium users"}, + response.choices[0].message, + { + "role": "tool", + "tool_call_id": tool_call.id, + "content": json.dumps(tool_result.json()) + } + ] + ) +---- + + +=== Claude code cli + +[source,bash] +---- +# Configure gateway with MCP +export CLAUDE_API_BASE="https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1" +export ANTHROPIC_API_KEY="your-redpanda-token" + +# Claude Code automatically discovers MCP tools from gateway +claude code + +# Agent can now use aggregated MCP tools +---- + + +=== LangChain + +[source,python] +---- +from langchain_openai import ChatOpenAI +from langchain.agents import initialize_agent, Tool + +# Initialize LLM with gateway +llm = ChatOpenAI( + base_url="https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1", + api_key=os.getenv("REDPANDA_CLOUD_TOKEN"), + default_headers={ + "rp-aigw-id": os.getenv("GATEWAY_ID") + } +) + +# Fetch MCP tools from gateway +# PLACEHOLDER: LangChain-specific integration code + +# Create agent with MCP tools +agent = initialize_agent( + tools=mcp_tools, + llm=llm, + agent="openai-tools", + verbose=True +) + +# Agent can now use MCP tools +response = agent.run("Find all premium users in the database") +---- + + +== Next steps + +* *Configure MCP Servers* → [MCP Server Administration Guide](// PLACEHOLDER: link) +* *Write Orchestrator Workflows* → [Orchestrator Examples](// PLACEHOLDER: link) +* *Monitor MCP Usage* → [Observability: MCP Metrics](// PLACEHOLDER: link) +* *Optimize Token Costs* → [Cost Optimization Guide](// PLACEHOLDER: link) +* *Build Agentic Workflows* → [Agent Patterns Guide](// PLACEHOLDER: link) + +== Related pages + +* [Quickstart](// PLACEHOLDER: link) +* [CEL Routing](// PLACEHOLDER: link) +* [Observability: Logs](// PLACEHOLDER: link) +* [Security & Data Handling](// PLACEHOLDER: link) diff --git a/modules/ai-agents/partials/migration-guide.adoc b/modules/ai-agents/partials/migration-guide.adoc new file mode 100644 index 000000000..ffdd54a17 --- /dev/null +++ b/modules/ai-agents/partials/migration-guide.adoc @@ -0,0 +1,866 @@ += Migration guide: from direct provider integration to AI gateway + +== Overview + +This guide helps you migrate existing applications from direct LLM provider integrations (OpenAI, Anthropic, etc.) to Redpanda AI Gateway. The migration is designed to be *incremental and reversible*, allowing you to test thoroughly before fully committing. + +*Migration Time*: 10-30 minutes for most applications +*Downtime Required*: None (supports parallel operation) +*Rollback Difficulty*: Easy (feature flag or environment variable) + +== Prerequisites + +Before migrating, ensure you have: +* ✅ AI Gateway configured in your Redpanda Cloud account +* ✅ Providers and models enabled (see [Admin Guide: Providers](// PLACEHOLDER: link)) +* ✅ Gateway created with appropriate policies (see [Gateway Creation Guide](// PLACEHOLDER: link)) +* ✅ Your gateway ID (`rp-aigw-id` header value) +* ✅ Your gateway endpoint URL + + + +== Migration strategy + +=== Recommended approach: parallel operation + +Run both direct and gateway-routed requests simultaneously to validate behavior before full cutover. + +[source,text] +---- +┌─────────────────┐ +│ Application │ +└────────┬────────┘ + │ + ┌────▼─────┐ + │ Feature │ + │ Flag │ + └────┬─────┘ + │ + ┌────▼──────────────┐ + │ │ +┌───▼─────┐ ┌─────▼─────┐ +│ Direct │ │ Gateway │ +│Provider │ │ Route │ +└─────────┘ └───────────┘ +---- + + +*Benefits*: +* No downtime +* Easy rollback +* Compare results side-by-side +* Gradual traffic shift + +== Step-by-step migration + +=== Step 1: add environment variables + +Add gateway configuration to your environment without removing existing provider keys (yet). + +*.env (or equivalent)* +[source,bash] +---- +# Existing (keep these for now) +OPENAI_API_KEY=sk-... +ANTHROPIC_API_KEY=sk-ant-... + +# New gateway configuration +REDPANDA_AI_GATEWAY_URL=https://{GATEWAY_ENDPOINT} +REDPANDA_AI_GATEWAY_ID={GATEWAY_ID} +REDPANDA_AI_GATEWAY_TOKEN={YOUR_TOKEN} + +# Feature flag (start with gateway disabled) +USE_AI_GATEWAY=false +---- + + +=== Step 2: update your code + +==== Option a: OpenAI SDK (recommended for most use cases) + +*Before (Direct OpenAI)* +[source,python] +---- +from openai import OpenAI + +client = OpenAI( + api_key=os.getenv("OPENAI_API_KEY") +) + +response = client.chat.completions.create( + model="gpt-4o", + messages=[{"role": "user", "content": "Hello"}] +) +---- + + +*After (Gateway-Routed with Feature Flag)* +[source,python] +---- +from openai import OpenAI +import os + +# Feature flag determines which client to use +use_gateway = os.getenv("USE_AI_GATEWAY", "false").lower() == "true" + +if use_gateway: + client = OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} + ) + model = "openai/gpt-4o" # Add vendor prefix +else: + client = OpenAI( + api_key=os.getenv("OPENAI_API_KEY") + ) + model = "gpt-4o" # Original model name + +response = client.chat.completions.create( + model=model, + messages=[{"role": "user", "content": "Hello"}] +) +---- + + +*Better: Abstraction Function* +[source,python] +---- +from openai import OpenAI +import os + +def get_llm_client(): + """Returns configured OpenAI client (direct or gateway-routed)""" + use_gateway = os.getenv("USE_AI_GATEWAY", "false").lower() == "true" + + if use_gateway: + return OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} + ) + else: + return OpenAI(api_key=os.getenv("OPENAI_API_KEY")) + +def get_model_name(base_model: str) -> str: + """Returns model name with vendor prefix if using gateway""" + use_gateway = os.getenv("USE_AI_GATEWAY", "false").lower() == "true" + return f"openai/{base_model}" if use_gateway else base_model + +# Usage +client = get_llm_client() +response = client.chat.completions.create( + model=get_model_name("gpt-4o"), + messages=[{"role": "user", "content": "Hello"}] +) +---- + + +==== Option b: Anthropic SDK + +*Before (Direct Anthropic)* +[source,python] +---- +from anthropic import Anthropic + +client = Anthropic( + api_key=os.getenv("ANTHROPIC_API_KEY") +) + +response = client.messages.create( + model="claude-sonnet-3.5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}] +) +---- + + +*After (Gateway via OpenAI-Compatible Wrapper)* + +Because AI Gateway provides an OpenAI-compatible endpoint, we recommend migrating Anthropic SDK usage to OpenAI SDK for consistency: + +[source,python] +---- +from openai import OpenAI +import os + +use_gateway = os.getenv("USE_AI_GATEWAY", "false").lower() == "true" + +if use_gateway: + # Use OpenAI SDK with gateway + client = OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} + ) + + response = client.chat.completions.create( + model="anthropic/claude-sonnet-3.5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}] + ) +else: + # Keep existing Anthropic SDK + from anthropic import Anthropic + client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) + + response = client.messages.create( + model="claude-sonnet-3.5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}] + ) +---- + + +*Alternative: Keep Anthropic SDK with base_url Override* + +// PLACEHOLDER: Verify if Anthropic SDK supports base_url override for OpenAI-compatible endpoints + +[source,python] +---- +from anthropic import Anthropic + +use_gateway = os.getenv("USE_AI_GATEWAY", "false").lower() == "true" + +if use_gateway: + client = Anthropic( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), # If supported + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} + ) +else: + client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) +---- + + +==== Option c: multiple providers + +*Before (Separate SDKs)* +[source,python] +---- +from openai import OpenAI +from anthropic import Anthropic + +openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) +anthropic_client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) + +# Different code paths +if use_openai: + response = openai_client.chat.completions.create(...) +else: + response = anthropic_client.messages.create(...) +---- + + +*After (Unified via Gateway)* +[source,python] +---- +from openai import OpenAI + +# Single client for all providers +client = OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} +) + +# Same code, different models +if use_openai: + response = client.chat.completions.create( + model="openai/gpt-4o", + messages=[...] + ) +else: + response = client.chat.completions.create( + model="anthropic/claude-sonnet-3.5", + messages=[...] + ) +---- + + +=== Step 3: test gateway connection + +Before changing the feature flag, verify gateway connectivity: + +*Python Test Script* +[source,python] +---- +from openai import OpenAI +import os + +def test_gateway_connection(): + client = OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} + ) + + try: + response = client.chat.completions.create( + model="openai/gpt-4o-mini", # Use cheap model for testing + messages=[{"role": "user", "content": "Test"}], + max_tokens=10 + ) + print("✅ Gateway connection successful") + print(f"Response: {response.choices[0].message.content}") + return True + except Exception as e: + print(f"❌ Gateway connection failed: {e}") + return False + +if __name__ == "__main__": + test_gateway_connection() +---- + + +*Expected Output*: +[source,text] +---- +✅ Gateway connection successful +Response: Hello +---- + + +*Common Issues*: +* `401 Unauthorized` → Check `REDPANDA_AI_GATEWAY_TOKEN` +* `404 Not Found` → Check `REDPANDA_AI_GATEWAY_URL` (should end with `/v1/chat/completions` or base path) +* `Model not found` → Ensure model is enabled in gateway configuration +* No `rp-aigw-id` header → Verify header is set in `default_headers` + +See [Troubleshooting Guide](// PLACEHOLDER: link) for more details. + +=== Step 4: verify in observability dashboard + +After successful test: +1. Open AI Gateway observability dashboard +2. Navigate to // PLACEHOLDER: specific UI path, e.g., "Gateways → {GATEWAY_NAME} → Logs" +3. Verify your test request appears +4. Check fields: + * ✅ Model: `openai/gpt-4o-mini` + * ✅ Provider: OpenAI + * ✅ Status: 200 + * ✅ Token count: ~10 prompt + ~10 completion + * ✅ Cost: // PLACEHOLDER: expected cost + +*If request doesn't appear*: Check [End-to-End Validation Guide](// PLACEHOLDER: link) + +=== Step 5: enable gateway for subset of traffic + +Gradually roll out gateway usage: + +*Staged Rollout Strategy*: +1. *Week 1*: Internal testing only (dev team accounts) +2. *Week 2*: 10% of production traffic +3. *Week 3*: 50% of production traffic +4. *Week 4*: 100% of production traffic + +*Implementation Options*: + +*Option A: Environment-Based* +[source,python] +---- +# Enable gateway in staging first +use_gateway = os.getenv("ENVIRONMENT") in ["staging", "production"] +---- + + +*Option B: Percentage-Based* +[source,python] +---- +import random + +# Route 10% of traffic through gateway +use_gateway = random.random() < 0.10 +---- + + +*Option C: User-Based* +[source,python] +---- +# Enable for internal users first +use_gateway = user.email.endswith("@yourcompany.com") +---- + + +*Option D: Feature Flag Service* (recommended) +[source,python] +---- +# LaunchDarkly, Split.io, etc. +use_gateway = feature_flags.is_enabled("ai-gateway", user_context) +---- + + +=== Step 6: monitor and compare + +During parallel operation, compare metrics: + +*Metrics to Monitor*: +| Metric | Direct | Gateway | Notes | +|--------|--------|---------|-------| +| Success rate | // track | // track | Should be identical | +| Latency p50 | // track | // track | Gateway adds ~// PLACEHOLDER: Xms | +| Latency p99 | // track | // track | Watch for outliers | +| Error rate | // track | // track | Should be identical | +| Cost per 1K requests | // track | // track | Compare estimated costs | + +*Monitoring Code Example*: +[source,python] +---- +import time + +def call_llm_with_metrics(use_gateway: bool, model: str, messages: list): + start_time = time.time() + + try: + client = get_llm_client(use_gateway) + response = client.chat.completions.create( + model=model, + messages=messages + ) + + latency = time.time() - start_time + + # Log metrics + metrics.record("llm.request.success", 1, tags={ + "routing": "gateway" if use_gateway else "direct", + "model": model + }) + metrics.record("llm.request.latency", latency, tags={ + "routing": "gateway" if use_gateway else "direct" + }) + + return response + + except Exception as e: + metrics.record("llm.request.error", 1, tags={ + "routing": "gateway" if use_gateway else "direct", + "error": str(e) + }) + raise +---- + + +=== Step 7: full cutover + +Once metrics confirm gateway reliability: + +1. *Set feature flag to 100%*: + ```bash + USE_AI_GATEWAY=true + ``` + +2. *Deploy updated configuration* + +3. *Monitor for 24-48 hours* + +4. *Remove direct provider credentials* (optional, for security): + ```bash + # .env + # OPENAI_API_KEY=sk-... # Remove after confirming gateway stability + # ANTHROPIC_API_KEY=sk-ant-... # Remove after confirming gateway stability + + REDPANDA_AI_GATEWAY_URL=https://{GATEWAY_ENDPOINT} + REDPANDA_AI_GATEWAY_ID={GATEWAY_ID} + REDPANDA_AI_GATEWAY_TOKEN={YOUR_TOKEN} + ``` + +5. *Remove direct integration code* (optional, for cleanup): + ```python + # Remove feature flag logic, keep only gateway path + client = OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} + ) + ``` + +== Rollback procedure + +If issues arise, rollback is simple: + +*Emergency Rollback (< 1 minute)*: +[source,bash] +---- +# Set feature flag back to false +USE_AI_GATEWAY=false + +# Restart application (if needed) +---- + + +*Gradual Rollback*: +[source,python] +---- +# Reduce gateway traffic percentage +use_gateway = random.random() < 0.50 # Back to 50% +use_gateway = random.random() < 0.10 # Back to 10% +use_gateway = False # Back to 0% +---- + + +*Keep direct provider credentials until you're confident in gateway stability.* + +== Framework-specific migration + +=== LangChain + +*Before* +[source,python] +---- +from langchain_openai import ChatOpenAI + +llm = ChatOpenAI( + model="gpt-4o", + api_key=os.getenv("OPENAI_API_KEY") +) +---- + + +*After* +[source,python] +---- +from langchain_openai import ChatOpenAI + +use_gateway = os.getenv("USE_AI_GATEWAY", "false").lower() == "true" + +if use_gateway: + llm = ChatOpenAI( + model="openai/gpt-4o", + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} + ) +else: + llm = ChatOpenAI( + model="gpt-4o", + api_key=os.getenv("OPENAI_API_KEY") + ) +---- + + +=== LlamaIndex + +*Before* +[source,python] +---- +from llama_index.llms.openai import OpenAI + +llm = OpenAI(model="gpt-4o") +---- + + +*After* +[source,python] +---- +from llama_index.llms.openai import OpenAI + +use_gateway = os.getenv("USE_AI_GATEWAY", "false").lower() == "true" + +if use_gateway: + llm = OpenAI( + model="openai/gpt-4o", + api_base=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + additional_kwargs={"headers": {"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")}} + ) +else: + llm = OpenAI(model="gpt-4o") +---- + + +// PLACEHOLDER: Verify LlamaIndex syntax for custom headers + +=== Vercel AI SDK (javascript/typescript) + +*Before* +[source,typescript] +---- +import { openai } from '@ai-sdk/openai'; + +const model = openai('gpt-4o'); +---- + + +*After* +[source,typescript] +---- +import { openai } from '@ai-sdk/openai'; + +const useGateway = process.env.USE_AI_GATEWAY === 'true'; + +const model = useGateway + ? openai('openai/gpt-4o', { + baseURL: process.env.REDPANDA_AI_GATEWAY_URL, + apiKey: process.env.REDPANDA_AI_GATEWAY_TOKEN, + headers: { + 'rp-aigw-id': process.env.REDPANDA_AI_GATEWAY_ID, + }, + }) + : openai('gpt-4o'); +---- + + +// PLACEHOLDER: Verify Vercel AI SDK syntax + +== Migration checklist + +Use this checklist to track your migration: + +* [ ] *Prerequisites* + * [ ] Gateway configured and tested + * [ ] Providers enabled + * [ ] Models enabled + * [ ] Gateway ID and endpoint URL obtained + +* [ ] *Code Changes* + * [ ] Environment variables added + * [ ] Feature flag implemented + * [ ] Client initialization updated + * [ ] Model name prefix added (vendor/model_id) + * [ ] Headers added (rp-aigw-id) + +* [ ] *Testing* + * [ ] Gateway connection test passes + * [ ] Test request visible in observability dashboard + * [ ] Integration tests pass with gateway + * [ ] End-to-end tests pass with gateway + +* [ ] *Staged Rollout* + * [ ] Week 1: Internal testing (dev team only) + * [ ] Week 2: 10% production traffic + * [ ] Week 3: 50% production traffic + * [ ] Week 4: 100% production traffic + +* [ ] *Monitoring* + * [ ] Success rate comparison (direct vs gateway) + * [ ] Latency comparison (direct vs gateway) + * [ ] Error rate comparison (direct vs gateway) + * [ ] Cost comparison (direct vs gateway) + +* [ ] *Cleanup* (optional, after 30 days stable) + * [ ] Remove direct provider credentials + * [ ] Remove feature flag logic + * [ ] Update documentation + * [ ] Archive direct integration code + +== Common migration issues + +=== Issue: "model not found" error + +*Symptom*: +[source,text] +---- +Error: Model 'openai/gpt-4o' not found +---- + + +*Causes*: +1. Model not enabled in gateway configuration +2. Wrong model name format (missing vendor prefix) +3. Typo in model name + +*Solution*: +1. Verify model is enabled: // PLACEHOLDER: UI path or CLI command +2. Confirm format: `vendor/model_id` (e.g., `openai/gpt-4o`, not `gpt-4o`) +3. Check supported models: // PLACEHOLDER: link to model catalog + +=== Issue: missing `rp-aigw-id` header + +*Symptom*: +[source,text] +---- +Error: Missing required header 'rp-aigw-id' +---- + + +*Solution*: +[source,python] +---- +# Ensure header is set in default_headers +client = OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} # ← Required +) +---- + + +=== Issue: higher latency than expected + +*Expected Gateway Overhead*: // PLACEHOLDER: Xms p50, Yms p99 + +*If latency is significantly higher*: +1. Check geographic routing (gateway → provider region) +2. Verify provider pool configuration (no unnecessary fallbacks) +3. Review CEL routing complexity +4. Check for rate limiting (adds retry latency) + +*Solution*: See [Performance Optimization Guide](// PLACEHOLDER: link) + +=== Issue: requests not appearing in dashboard + +*Causes*: +1. Wrong gateway ID +2. Request failed before reaching gateway +3. UI delay (logs may take // PLACEHOLDER: Xs to appear) + +*Solution*: See [End-to-End Validation Guide](// PLACEHOLDER: link) + +=== Issue: different response format + +*Symptom*: Response structure differs between direct and gateway + +// PLACEHOLDER: Confirm if response format is identical to OpenAI API or if there are differences + +*Solution*: +* AI Gateway should return OpenAI-compatible responses +* If differences exist, file a support ticket with request ID from logs + +== Advanced migration scenarios + +=== Scenario: custom request timeouts + +*Before* +[source,python] +---- +client = OpenAI(api_key=..., timeout=30.0) +---- + + +*After* +[source,python] +---- +client = OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")}, + timeout=30.0 # Still supported +) +---- + + +=== Scenario: streaming responses + +// PLACEHOLDER: Verify streaming support + +*Before* +[source,python] +---- +stream = client.chat.completions.create( + model="gpt-4o", + messages=[...], + stream=True +) + +for chunk in stream: + print(chunk.choices[0].delta.content, end="") +---- + + +*After* +[source,python] +---- +stream = client.chat.completions.create( + model="openai/gpt-4o", # Add vendor prefix + messages=[...], + stream=True +) + +for chunk in stream: + print(chunk.choices[0].delta.content, end="") +---- + + +=== Scenario: custom headers (e.g., user tracking) + +*Before* +[source,python] +---- +response = client.chat.completions.create( + model="gpt-4o", + messages=[...], + extra_headers={"X-User-ID": user.id} +) +---- + + +*After* +[source,python] +---- +response = client.chat.completions.create( + model="openai/gpt-4o", + messages=[...], + extra_headers={ + "X-User-ID": user.id, # Custom headers still supported + "rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID") # Required gateway header + } +) +---- + + +*Note*: Gateway may use custom headers for routing (e.g., CEL expressions can reference `request.headers["X-User-ID"]`) + +== Post-migration benefits + +After successful migration, you gain: + +=== 1. simplified provider management +[source,python] +---- +# Switch providers with one config change (no code changes) +model = "anthropic/claude-sonnet-3.5" # Was openai/gpt-4o +---- + + +=== 2. unified observability +* All requests in one dashboard +* Cross-provider cost comparison +* Session reconstruction across models + +=== 3. automatic failover +* Configure once, benefit everywhere +* No application-level retry logic needed + +=== 4. cost controls +* Enforce budgets centrally +* Rate limit per team/customer +* No surprises in cloud bills + +=== 5. a/b testing +* Test new models without code changes +* Compare quality/cost/latency +* Gradual rollout via routing policies + +== Next steps + +* *Configure routing policies* → [CEL Routing Guide](// PLACEHOLDER: link) +* *Set up failover* → [Provider Pools Guide](// PLACEHOLDER: link) +* *Add rate limits* → [Rate Limiting Guide](// PLACEHOLDER: link) +* *Explore MCP* → [MCP Aggregation Guide](// PLACEHOLDER: link) +* *Optimize costs* → [Cost Optimization Guide](// PLACEHOLDER: link) + +== Get help + +If you encounter issues during migration: +* Check [Troubleshooting Guide](// PLACEHOLDER: link) +* Review [End-to-End Validation](// PLACEHOLDER: link) +* Contact support: // PLACEHOLDER: support email or portal +* Community: // PLACEHOLDER: Slack, Discord, or forum link + +''' + +*Related Pages*: +* [Quickstart](// PLACEHOLDER: link) +* [Troubleshooting](// PLACEHOLDER: link) +* [OpenAI Integration](// PLACEHOLDER: link) +* [Anthropic Integration](// PLACEHOLDER: link) +* [LangChain Integration](// PLACEHOLDER: link) diff --git a/modules/ai-agents/partials/observability-logs.adoc b/modules/ai-agents/partials/observability-logs.adoc new file mode 100644 index 000000000..fed03f2d5 --- /dev/null +++ b/modules/ai-agents/partials/observability-logs.adoc @@ -0,0 +1,631 @@ += Observability: request logs + +== Overview + +AI Gateway logs every LLM request that passes through it, capturing the full request/response history, token usage, cost, latency, and routing decisions. This page explains how to find, filter, and interpret request logs. + +*Use Logs For*: +* Debugging specific failed requests +* Reconstructing user conversation sessions +* Auditing what prompts were sent and responses received +* Understanding which provider handled a request +* Investigating latency spikes or errors for specific users + +*Use Metrics For*: Aggregate analytics, trends, cost tracking across time → See [Observability: Metrics](// PLACEHOLDER: link) + +== Where to find logs + +// PLACEHOLDER: Add exact UI navigation path + +1. *Navigate to Logs View*: + * Console → AI Gateway → // PLACEHOLDER: exact path + * Or: Gateway detail page → Logs tab + +2. *Select Gateway*: + * Filter by specific gateway, or view all gateways + * // PLACEHOLDER: screenshot of gateway selector + +3. *Set Time Range*: + * Default: Last 1 hour + * Options: Last 5 minutes, 1 hour, 24 hours, 7 days, 30 days, Custom + * // PLACEHOLDER: screenshot of time range picker + +== Request log fields + +Each log entry contains: + +=== Core request info + +| Field | Description | Example | +|-------|-------------|---------| +| *Request ID* | Unique identifier for this request | `req_abc123...` | +| *Timestamp* | When request was received (UTC) | `2025-01-11T14:32:10.123Z` | +| *Gateway ID* | Which gateway handled this request | `gw_abc123...` | +| *Gateway Name* | Human-readable gateway name | `production-gateway` | +| *Status* | HTTP status code | `200`, `400`, `429`, `500` | +| *Latency* | Total request duration (ms) | `1250ms` | + +=== Model & provider info + +| Field | Description | Example | +|-------|-------------|---------| +| *Requested Model* | Model specified in request | `openai/gpt-4o` | +| *Actual Model* | Model that handled request (may differ due to routing) | `anthropic/claude-sonnet-3.5` | +| *Provider* | Which provider handled the request | `OpenAI`, `Anthropic` | +| *Provider Pool* | Pool used (primary/fallback) | `primary`, `fallback` | +| *Fallback Triggered* | Whether fallback was used | `true`/`false` | +| *Fallback Reason* | Why fallback occurred | `rate_limit`, `timeout`, `5xx_error` | + +=== Token & cost info + +| Field | Description | Example | +|-------|-------------|---------| +| *Prompt Tokens* | Input tokens consumed | `523` | +| *Completion Tokens* | Output tokens generated | `187` | +| *Total Tokens* | Prompt + completion | `710` | +| *Estimated Cost* | Calculated cost for this request | `$0.0142` | +| *Cost Breakdown* | Per-token costs | `Prompt: $0.005, Completion: $0.0092` | + +=== Request content (expandable) + +| Field | Description | Notes | +|-------|-------------|-------| +| *Request Headers* | All headers sent | Includes `rp-aigw-id`, custom headers | +| *Request Body* | Full request payload | Includes messages, parameters | +| *Response Headers* | Headers returned | // PLACEHOLDER: Any gateway-specific headers? | +| *Response Body* | Full response payload | Includes message content, metadata | + +=== Routing & policy info + +| Field | Description | Example | +|-------|-------------|---------| +| *CEL Expression* | Routing rule applied (if any) | `request.headers["tier"] == "premium" ? ...` | +| *CEL Result* | Model selected by CEL | `openai/gpt-4o` | +| *Rate Limit Status* | Whether rate limited | `allowed`, `throttled`, `blocked` | +| *Spend Limit Status* | Whether budget exceeded | `allowed`, `blocked` | +| *Policy Stage* | Where request was processed/blocked | `rate_limit`, `routing`, `execution` | + +=== Error info (if applicable) + +| Field | Description | Example | +|-------|-------------|---------| +| *Error Code* | Gateway or provider error code | `RATE_LIMIT_EXCEEDED`, `MODEL_NOT_FOUND` | +| *Error Message* | Human-readable error | `Request rate limit exceeded for gateway` | +| *Provider Error* | Upstream provider error | `OpenAI API returned 429: Rate limit exceeded` | + +== Filter logs + +=== By gateway + +// PLACEHOLDER: Screenshot of gateway filter dropdown + +[source,text] +---- +Filter: Gateway = "production-gateway" +---- + + +Shows only requests for the selected gateway. + +*Use Case*: Isolate production traffic from staging + +=== By model + +// PLACEHOLDER: Screenshot of model filter + +[source,text] +---- +Filter: Model = "openai/gpt-4o" +---- + + +Shows only requests for specific model. + +*Use Case*: Compare quality/cost between models + +=== By provider + +[source,text] +---- +Filter: Provider = "OpenAI" +---- + + +Shows only requests handled by specific provider. + +*Use Case*: Investigate provider-specific issues + +=== By status + +[source,text] +---- +Filter: Status = "429" +---- + + +Shows only requests with specific HTTP status. + +*Common Filters*: +* `200`: Successful requests +* `400`: Bad requests (client errors) +* `401`: Authentication errors +* `429`: Rate limited requests +* `500`: Server errors +* `5xx`: All server errors + +*Use Case*: Find all failed requests + +=== By time range + +[source,text] +---- +Filter: Timestamp >= "2025-01-11T14:00:00Z" AND Timestamp <= "2025-01-11T15:00:00Z" +---- + + +*Use Case*: Investigate incident during specific time window + +=== By custom header + +[source,text] +---- +Filter: request.headers["x-user-id"] = "user_123" +---- + + +Shows only requests for specific user. + +*Use Case*: Debug user-reported issue + +=== By token range + +[source,text] +---- +Filter: Total Tokens > 10000 +---- + + +Shows only high-token requests. + +*Use Case*: Find expensive requests + +=== By latency + +[source,text] +---- +Filter: Latency > 5000ms +---- + + +Shows only slow requests. + +*Use Case*: Investigate performance issues + +=== Combined filters + +[source,text] +---- +Gateway = "production-gateway" +AND Status >= 500 +AND Timestamp >= "last 24 hours" +---- + + +Shows production server errors in last 24 hours. + +// PLACEHOLDER: Screenshot of multiple filters applied + +== Search logs + +=== Full-text search (if supported) + +// PLACEHOLDER: Confirm if full-text search is available + +[source,text] +---- +Search: "specific error message" +---- + + +Searches across all text fields (error messages, request/response content). + +=== Search by request content + +[source,text] +---- +Search in Request Body: "user's actual question" +---- + + +Find requests containing specific prompt text. + +*Use Case*: "A user said the AI gave a wrong answer about X" → Search for "X" in prompts + +=== Search by response content + +[source,text] +---- +Search in Response Body: "specific AI response phrase" +---- + + +Find responses containing specific text. + +*Use Case*: Find all requests where AI mentioned a competitor name + +== Inspect individual requests + +Click any log entry to expand full details. + +// PLACEHOLDER: Screenshot of expanded log entry + +=== Request details tab + +Shows: +* Full request headers +* Full request body (formatted JSON) +* All parameters (temperature, max_tokens, etc.) +* Custom headers used for routing + +*Example*: +[source,json] +---- +{ + "model": "openai/gpt-4o", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "What is Redpanda?" + } + ], + "temperature": 0.7, + "max_tokens": 500 +} +---- + + +=== Response details tab + +Shows: +* Full response headers +* Full response body (formatted JSON) +* Finish reason (`stop`, `length`, `content_filter`) +* Response metadata + +*Example*: +[source,json] +---- +{ + "id": "chatcmpl-...", + "choices": [ + { + "message": { + "role": "assistant", + "content": "Redpanda is a streaming data platform..." + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 24, + "completion_tokens": 87, + "total_tokens": 111 + } +} +---- + + +=== Routing details tab + +Shows: +* CEL expression evaluated (if any) +* CEL result (which model was selected) +* Provider pool used (primary/fallback) +* Fallback trigger reason (if applicable) +* Rate limit evaluation (allowed/blocked) +* Spend limit evaluation (allowed/blocked) + +*Example*: +[source,yaml] +---- +CEL Expression: | + request.headers["x-user-tier"] == "premium" + ? "openai/gpt-4o" + : "openai/gpt-4o-mini" + +CEL Result: "openai/gpt-4o" + +Provider Pool: primary +Fallback Triggered: false + +Rate Limit: allowed (45/100 requests used) +Spend Limit: allowed ($1,234 / $50,000 budget used) +---- + + +=== Performance details tab + +Shows: +* Total latency breakdown + * Gateway processing time: // PLACEHOLDER: Xms + * Provider API call time: // PLACEHOLDER: Xms + * Network time: // PLACEHOLDER: Xms +* Token generation rate (tokens/second) +* Time to first token (for streaming, if supported) + +*Example*: +[source,text] +---- +Total Latency: 1,250ms +├─ Gateway Processing: 12ms +├─ Provider API Call: 1,215ms +└─ Network Overhead: 23ms + +Token Generation Rate: 71 tokens/second +---- + + +== Common log analysis tasks + +=== Task 1: "why did this request fail?" + +1. *Find the request*: + * Filter by timestamp (when user reported issue) + * Or search by request content + * Or filter by custom header (user ID) + +2. *Check Status*: + * `400` → Client error (bad request format, invalid parameters) + * `401` → Authentication issue + * `404` → Model not found + * `429` → Rate limited + * `500`/`5xx` → Provider or gateway error + +3. *Check Error Message*: + * Gateway error: Issue with configuration, rate limits, etc. + * Provider error: Issue with upstream API (OpenAI, Anthropic, etc.) + +4. *Check Routing*: + * Was fallback triggered? (May indicate primary provider issue) + * Was CEL rule applied correctly? + +*Common Causes*: +* Model not enabled in gateway +* Rate limit exceeded +* Monthly budget exceeded +* Invalid API key for provider +* Provider outage/rate limit +* Malformed request + +=== Task 2: "reconstruct a user's conversation" + +1. *Filter by user*: + ``` + Filter: request.headers["x-user-id"] = "user_123" + ``` + +2. *Sort by timestamp* (ascending) + +3. *Review conversation flow*: + * Each request shows prompt + * Each response shows AI reply + * Reconstruct full conversation thread + +*Use Case*: User says "the AI contradicted itself" → View full conversation history + +=== Task 3: "why is latency high for this user?" + +1. *Find user's requests*: + ``` + Filter: request.headers["x-user-id"] = "user_123" + AND Latency > 3000ms + ``` + +2. *Check Performance Details*: + * Is gateway processing slow? (Likely CEL complexity) + * Is provider API slow? (Upstream latency) + * Is token generation rate normal? (Tokens/second) + +3. *Compare to other requests*: + * Filter for same model + * Compare latency percentiles + * Identify if issue is user-specific or model-wide + +*Common Causes*: +* Complex CEL routing rules +* Provider performance degradation +* Large context windows (high token count) +* Network issues + +=== Task 4: "which requests used the fallback provider?" + +1. *Filter by fallback*: + ``` + Filter: Fallback Triggered = true + ``` + +2. *Group by Fallback Reason*: + * Rate limit exceeded (primary provider throttled) + * Timeout (primary provider slow) + * 5xx error (primary provider error) + +3. *Analyze pattern*: + * Is fallback happening frequently? (May indicate primary provider issue) + * Is fallback successful? (Check status of fallback requests) + +*Use Case*: Verify failover is working as expected + +=== Task 5: "what did we spend on this customer today?" + +1. *Filter by customer*: + ``` + Filter: request.headers["x-customer-id"] = "customer_abc" + AND Timestamp >= "today" + ``` + +2. *Sum estimated costs* (if UI supports): + // PLACEHOLDER: Does UI have cost aggregation for filtered results? + * Total: $X.XX + * Breakdown by model + +3. *Export to CSV* (if supported): + // PLACEHOLDER: Is CSV export available? + * For detailed billing analysis + +*Use Case*: Chargeback/showback to customers + +== Log retention + +// PLACEHOLDER: Confirm log retention policy + +*Retention Period*: // PLACEHOLDER: e.g., 30 days, 90 days, configurable + +*After Retention Period*: +* Logs are deleted automatically +* Aggregate metrics retained longer (see [Metrics](// PLACEHOLDER: link)) + +*Export Logs* (if needed for longer retention): +// PLACEHOLDER: Is log export available? Via API? CSV? + +== Log export + +// PLACEHOLDER: Confirm export capabilities + +=== Export to CSV + +// PLACEHOLDER: Add UI path for export, or indicate not available + +1. Apply filters for desired logs +2. Click "Export to CSV" +3. Download includes all filtered logs with full fields + +=== Export via API + +// PLACEHOLDER: If API is available for log export + +[source,bash] +---- +curl https://{CLUSTER_ID}.cloud.redpanda.com/api/ai-gateway/logs \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -G \ + --data-urlencode "gateway_id=gw_abc123" \ + --data-urlencode "start_time=2025-01-11T00:00:00Z" \ + --data-urlencode "end_time=2025-01-11T23:59:59Z" +---- + + +=== Integration with observability platforms + +// PLACEHOLDER: Are there integrations with external platforms? + +*Supported Integrations* (if any): +* OpenTelemetry export → Send logs to Jaeger, Datadog, New Relic +* CloudWatch Logs → For AWS deployments +* // PLACEHOLDER: Others? + +See [Observability Integrations](// PLACEHOLDER: link) for setup guides. + +== Privacy & security + +=== What is logged + +// PLACEHOLDER: Confirm what is logged by default + +*Logged by Default*: +* Request headers (including custom headers) +* Request body (full prompt content) +* Response body (full AI response) +* Token usage, cost, latency +* Routing decisions, policy evaluations + +*Not Logged* (if applicable): +* // PLACEHOLDER: Anything redacted? API keys? Specific headers? + +=== Redaction options + +// PLACEHOLDER: Are there options to redact PII or sensitive data? + +*If Redaction is Supported*: +* Configure redaction rules for specific fields +* Mask PII (email addresses, phone numbers, etc.) +* Redact custom header values + +Example: +[source,yaml] +---- +# PLACEHOLDER: Actual configuration format +redaction: + - field: request.headers.x-api-key + action: mask + - field: request.body.messages[].content + pattern: "\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b" # Email regex + action: replace + replacement: "[REDACTED_EMAIL]" +---- + + +=== Access control + +// PLACEHOLDER: Who can view logs? RBAC? + +*Permissions Required*: +* View logs: // PLACEHOLDER: role/permission name +* Export logs: // PLACEHOLDER: role/permission name + +*Audit Trail*: +* Log access is audited (who viewed which logs, when) +* // PLACEHOLDER: Where to find audit trail? + +== Troubleshoot log issues + +=== Issue: "logs not appearing for my request" + +*Possible Causes*: +1. Log ingestion delay (wait // PLACEHOLDER: Xs) +2. Wrong gateway ID filter +3. Request failed before reaching gateway (authentication error) +4. Time range filter too narrow + +*Solution*: +1. Wait a moment and refresh +2. Remove all filters, search by timestamp +3. Check client-side error logs +4. Expand time range to "Last 1 hour" + +=== Issue: "missing request/response content" + +*Possible Causes*: +1. Payload too large (// PLACEHOLDER: size limit?) +2. Redaction rules applied +3. // PLACEHOLDER: Other reasons? + +*Solution*: +// PLACEHOLDER: How to retrieve full content if truncated? + +=== Issue: "cost estimate incorrect" + +*Possible Causes*: +1. Cost estimate based on public pricing (may differ from your contract) +2. Provider changed pricing +3. // PLACEHOLDER: Other reasons? + +*Note*: Cost estimates are approximate. Use provider invoices for billing. + +== Next steps + +* *Aggregate Analytics* → [Observability: Metrics](// PLACEHOLDER: link) +* *Set Up Alerts* → [Alerting Guide](// PLACEHOLDER: link) +* *Export to SIEM* → [Security Integration Guide](// PLACEHOLDER: link) +* *Cost Attribution* → [Cost Tracking Guide](// PLACEHOLDER: link) +* *Troubleshoot Errors* → [Troubleshooting Guide](// PLACEHOLDER: link) + +== Related pages + +* [Observability: Metrics](// PLACEHOLDER: link) +* [End-to-End Validation](// PLACEHOLDER: link) +* [Troubleshooting Common Issues](// PLACEHOLDER: link) +* [CEL Routing Deep Dive](// PLACEHOLDER: link) diff --git a/modules/ai-agents/partials/observability-metrics.adoc b/modules/ai-agents/partials/observability-metrics.adoc new file mode 100644 index 000000000..834f8c506 --- /dev/null +++ b/modules/ai-agents/partials/observability-metrics.adoc @@ -0,0 +1,751 @@ += Observability: metrics & analytics + +== Overview + +AI Gateway provides aggregate metrics and analytics dashboards to help you understand usage patterns, costs, performance, and errors across all your LLM traffic. + +*Use Metrics For*: +* Cost tracking and budget management +* Usage trends over time +* Performance monitoring (latency, error rates) +* Capacity planning +* Model/provider comparison + +*Use Logs For*: Debugging specific requests, viewing full prompts/responses → See [Observability: Logs](// PLACEHOLDER: link) + +== Where to find metrics + +// PLACEHOLDER: Add exact UI navigation path + +1. *Navigate to Analytics Dashboard*: + * Console → AI Gateway → // PLACEHOLDER: exact path + * Or: Gateway detail page → Analytics tab + +2. *Select Gateway* (optional): + * View all gateways (org-wide metrics) + * Or filter to specific gateway + +3. *Set Time Range*: + * Default: Last 7 days + * Options: Last 24 hours, 7 days, 30 days, 90 days, Custom + * // PLACEHOLDER: screenshot of time range picker + +== Key metrics + +=== Request volume + +*What it shows*: Total number of requests over time + +// PLACEHOLDER: Screenshot of request volume graph + +*Graph Type*: Time series line chart + +*Filters*: +* By gateway +* By model +* By provider +* By status (success/error) + +*Use Cases*: +* Identify usage patterns (peak hours, days of week) +* Detect traffic spikes or drops +* Capacity planning + +*Example Insights*: +* "Traffic doubles every Monday morning at 9am" → Scale infrastructure +* "Staging gateway has more traffic than prod" → Investigate runaway testing + +=== Token usage + +*What it shows*: Prompt, completion, and total tokens consumed + +// PLACEHOLDER: Screenshot of token usage graph + +*Graph Type*: Stacked area chart (prompt vs completion tokens) + +*Metrics*: +* Total tokens +* Prompt tokens (input) +* Completion tokens (output) +* Tokens per request (average) + +*Breakdowns*: +* By gateway +* By model +* By provider + +*Use Cases*: +* Understand cost drivers (prompt vs completion tokens) +* Identify verbose prompts or responses +* Optimize token usage + +*Example Insights*: +* "90% of tokens are completion tokens" → Responses are verbose, optimize max_tokens +* "Staging uses 10x more tokens than prod" → Investigate test suite + +=== Estimated spend + +*What it shows*: Calculated cost based on token usage and public pricing + +// PLACEHOLDER: Screenshot of cost tracking dashboard + +*Graph Type*: Time series line chart with cost breakdown + +*Metrics*: +* Total estimated spend +* Spend by model +* Spend by provider +* Spend by gateway +* Cost per 1K requests +* Cost per 1M tokens + +*Breakdowns*: +* By gateway (for chargeback/showback) +* By model (for cost optimization) +* By provider (for negotiation leverage) +* By custom header (if configured, e.g., `x-customer-id`) + +*Use Cases*: +* Budget tracking ("Are we staying under $50K/month?") +* Cost attribution ("Which team spent the most?") +* Model comparison ("Is Claude cheaper than GPT-4 for our use case?") +* Forecasting ("At this rate, we'll spend $X next month") + +*Important Notes*: +* *Estimates based on public pricing* (may differ from your contract) +* *Not a substitute for provider invoices* (use for approximation only) +* *Update frequency*: // PLACEHOLDER: Real-time? Hourly? Daily? + +*Example Insights*: +* "Customer A accounts for 60% of spend" → Consider rate limits or tiered pricing +* "GPT-4o is 3x more expensive than Claude Sonnet for similar quality" → Optimize routing + +=== Latency + +*What it shows*: Request duration from gateway to provider and back + +// PLACEHOLDER: Screenshot of latency histogram + +*Metrics*: +* p50 (median) latency +* p95 latency +* p99 latency +* Min/max latency +* Average latency + +*Breakdowns*: +* By gateway +* By model +* By provider +* By token range (longer responses = higher latency) + +*Use Cases*: +* Identify slow models or providers +* Set SLO targets (e.g., "p95 < 2 seconds") +* Detect performance regressions + +*Example Insights*: +* "GPT-4o p99 latency spiked to 10 seconds yesterday" → Investigate provider issue +* "Claude Sonnet is 30% faster than GPT-4o for same prompts" → Optimize for latency + +*Latency Components* (if available): +// PLACEHOLDER: Does gateway show latency breakdown? +* Gateway processing time +* Provider API time +* Network time + +=== Error rate + +*What it shows*: Percentage of failed requests over time + +// PLACEHOLDER: Screenshot of error rate graph + +*Metrics*: +* Total error rate (%) +* Errors by status code (400, 401, 429, 500, etc.) +* Errors by model +* Errors by provider + +*Graph Type*: Time series line chart with error percentage + +*Breakdowns*: +* By error type: + * Client errors (4xx) + * Rate limits (429) + * Server errors (5xx) + * Provider errors + * Gateway errors + +*Use Cases*: +* Detect provider outages +* Identify configuration issues (e.g., model not enabled) +* Monitor rate limit breaches + +*Example Insights*: +* "Error rate spiked to 15% at 2pm" → OpenAI outage, fallback to Anthropic worked +* "10% of requests fail with 'model not found'" → Model not enabled in gateway + +=== Success rate + +*What it shows*: Percentage of successful (200) requests over time + +*Metric*: `Success Rate = (Successful Requests / Total Requests) × 100` + +*Target*: Typically 99%+ for production workloads + +*Use Cases*: +* Monitor overall health +* Set up alerts (e.g., "Alert if success rate < 95%") + +=== Fallback rate + +*What it shows*: Percentage of requests that used fallback provider + +// PLACEHOLDER: Screenshot of fallback rate graph + +*Metric*: `Fallback Rate = (Fallback Requests / Total Requests) × 100` + +*Breakdowns*: +* By fallback reason: + * Rate limit exceeded + * Timeout + * 5xx error + +*Use Cases*: +* Monitor primary provider reliability +* Verify fallback is working +* Identify when to renegotiate rate limits + +*Example Insights*: +* "Fallback rate increased to 20% yesterday" → OpenAI hit rate limits, time to increase quota +* "Zero fallbacks in 30 days" → Fallback config may not be working, or primary provider is very reliable + +== Dashboard views + +=== Overview dashboard + +*Shows*: High-level metrics across all gateways + +// PLACEHOLDER: Screenshot of overview dashboard + +*Widgets*: +* Total requests (last 24h, 7d, 30d) +* Total spend (last 24h, 7d, 30d) +* Success rate (current) +* Average latency (current) +* Top 5 models by request volume +* Top 5 gateways by spend + +*Use Case*: Executive view, health at a glance + +=== Gateway dashboard + +*Shows*: Metrics for a specific gateway + +// PLACEHOLDER: Screenshot of gateway dashboard + +*Widgets*: +* Request volume (time series) +* Token usage (time series) +* Estimated spend (time series) +* Latency percentiles (histogram) +* Error rate (time series) +* Model breakdown (pie chart) +* Provider breakdown (pie chart) + +*Use Case*: Team-specific monitoring, gateway optimization + +=== Model comparison dashboard + +*Shows*: Side-by-side comparison of models + +// PLACEHOLDER: Screenshot of model comparison + +*Metrics per Model*: +* Request count +* Total tokens +* Estimated cost +* Cost per 1K requests +* Average latency +* Error rate + +*Use Case*: Evaluate whether to switch models (cost vs performance) + +*Example*: +| Model | Requests | Avg Latency | Cost per 1K | Error Rate | +|-------|----------|-------------|-------------|------------| +| openai/gpt-4o | 10,000 | 1.2s | $5.00 | 0.5% | +| anthropic/claude-sonnet-3.5 | 5,000 | 0.9s | $3.50 | 0.3% | +| openai/gpt-4o-mini | 20,000 | 0.7s | $0.50 | 1.0% | + +*Insight*: Claude Sonnet is 25% faster and 30% cheaper than GPT-4o with better reliability + +=== Provider comparison dashboard + +*Shows*: Side-by-side comparison of providers + +*Metrics per Provider*: +* Request count +* Total spend +* Average latency +* Error rate +* Fallback trigger rate + +*Use Case*: Evaluate provider reliability, negotiate contracts + +=== Cost breakdown dashboard + +*Shows*: Detailed cost analysis + +// PLACEHOLDER: Screenshot of cost breakdown + +*Widgets*: +* Spend by gateway (stacked bar chart) +* Spend by model (pie chart) +* Spend by provider (pie chart) +* Spend by custom dimension (if configured, e.g., customer ID) +* Spend trend (time series with forecast) +* Budget utilization (progress bar: $X / $Y monthly limit) + +*Use Case*: FinOps, budget management, chargeback/showback + +== Filter & group + +=== Filter by gateway + +[source,text] +---- +Filter: Gateway = "production-gateway" +---- + + +Shows metrics for specific gateway only. + +*Use Case*: Isolate prod from staging metrics + +=== Filter by model + +[source,text] +---- +Filter: Model = "openai/gpt-4o" +---- + + +Shows metrics for specific model only. + +*Use Case*: Evaluate model performance in isolation + +=== Filter by provider + +[source,text] +---- +Filter: Provider = "OpenAI" +---- + + +Shows metrics for specific provider only. + +*Use Case*: Evaluate provider reliability + +=== Filter by status + +[source,text] +---- +Filter: Status = "200" // Only successful requests +Filter: Status >= "500" // Only server errors +---- + + +*Use Case*: Focus on errors, or calculate success rate + +=== Filter by custom dimension + +// PLACEHOLDER: Confirm if custom dimensions are supported for filtering + +[source,text] +---- +Filter: request.headers["x-customer-id"] = "customer_abc" +---- + + +Shows metrics for specific customer. + +*Use Case*: Customer-specific cost tracking for chargeback + +=== Group by dimension + +*Common Groupings*: +* Group by Gateway +* Group by Model +* Group by Provider +* Group by Status +* Group by Hour/Day/Week/Month (time aggregation) + +*Example*: "Show me spend grouped by model, for production gateway, over last 30 days" + +== Alerting + +// PLACEHOLDER: Confirm if alerting is supported + +*If Alerting is Supported*: + +=== Alert types + +*Budget Alerts*: +* Alert when spend exceeds X% of monthly budget +* Alert when spend grows Y% week-over-week + +*Performance Alerts*: +* Alert when error rate > X% +* Alert when p99 latency > Xms +* Alert when success rate < X% + +*Usage Alerts*: +* Alert when request volume drops (potential outage) +* Alert when fallback rate > X% (primary provider issue) + +=== Alert channels + +// PLACEHOLDER: Supported notification channels +* Email +* Slack +* PagerDuty +* Webhook +* // PLACEHOLDER: Others? + +=== Example alert configuration + +[source,yaml] +---- +# PLACEHOLDER: Actual alert configuration format +alerts: + - name: "High Error Rate" + condition: error_rate > 5% + duration: 5 minutes + channels: [slack, email] + + - name: "Budget Threshold" + condition: monthly_spend > 80% of budget + channels: [email] + + - name: "Latency Spike" + condition: p99_latency > 5000ms + duration: 10 minutes + channels: [pagerduty] +---- + + +See [Alerting Guide](// PLACEHOLDER: link) for detailed setup. + +== Export metrics + +// PLACEHOLDER: Confirm export capabilities + +=== Export to CSV + +1. Apply filters for desired metrics +2. Click "Export to CSV" +3. Download includes time series data + +*Use Case*: Import into spreadsheet for analysis, reporting + +=== Export via API + +// PLACEHOLDER: If API is available for metrics + +[source,bash] +---- +curl https://{CLUSTER_ID}.cloud.redpanda.com/api/ai-gateway/metrics \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -G \ + --data-urlencode "gateway_id=gw_abc123" \ + --data-urlencode "start_time=2025-01-01T00:00:00Z" \ + --data-urlencode "end_time=2025-01-31T23:59:59Z" \ + --data-urlencode "metric=requests,tokens,cost" +---- + + +*Response*: +[source,json] +---- +{ + "gateway_id": "gw_abc123", + "start_time": "2025-01-01T00:00:00Z", + "end_time": "2025-01-31T23:59:59Z", + "metrics": { + "requests": 1000000, + "tokens": 500000000, + "estimated_cost": 2500.00 + } +} +---- + + +=== Integration with observability platforms + +// PLACEHOLDER: OpenTelemetry support? Other integrations? + +*Supported Integrations* (if any): +* *Prometheus*: Metrics endpoint for scraping +* *OpenTelemetry*: Export metrics to OTel collector +* *Datadog*: Direct integration +* *Grafana*: Pre-built dashboards +* // PLACEHOLDER: Others? + +See [Observability Integrations](// PLACEHOLDER: link) for setup guides. + +== Common analysis tasks + +=== Task 1: "are we staying within budget?" + +1. *View Cost Breakdown Dashboard* +2. *Check Budget Utilization Widget*: + * Current spend: $X + * Monthly budget: $Y + * Utilization: X% + * Days remaining in month: Z +3. *Forecast*: + * At current rate: $X × (30 / days_elapsed) + * On track to exceed budget? Yes/No + +*Action*: +* If approaching limit: Adjust rate limits, optimize models, pause non-prod usage +* If well under budget: Opportunity to test more expensive models + +=== Task 2: "which team is using the most resources?" + +1. *Filter by Gateway* (assuming one gateway per team) +2. *Sort by Spend* (descending) +3. *View Table*: + +| Gateway | Requests | Tokens | Spend | % of Total | +|---------|----------|--------|-------|------------| +| team-ml | 500K | 250M | $1,250 | 50% | +| team-product | 300K | 150M | $750 | 30% | +| team-eng | 200K | 100M | $500 | 20% | + +*Action*: Chargeback costs to teams, or investigate high-usage teams + +=== Task 3: "is this model worth the extra cost?" + +1. *Open Model Comparison Dashboard* +2. *Select Models to Compare*: + * Expensive model: `openai/gpt-4o` + * Cheap model: `openai/gpt-4o-mini` +3. *Compare Metrics*: + +| Metric | GPT-4o | GPT-4o-mini | Difference | +|--------|--------|-------------|------------| +| Cost per 1K requests | $5.00 | $0.50 | *10x* | +| Avg Latency | 1.2s | 0.7s | 58% *faster* (mini) | +| Error Rate | 0.5% | 1.0% | 2x errors (mini) | + +*Decision*: If mini's error rate is acceptable, save 10x on costs + +=== Task 4: "why did costs spike yesterday?" + +1. *View Cost Trend Graph* +2. *Identify Spike* (e.g., Jan 10th: $500 vs usual $100) +3. *Drill Down*: + * *By Gateway*: Which gateway caused the spike? + * *By Model*: Did someone switch to expensive model? + * *By Hour*: What time did spike occur? +4. *Cross-Reference with Logs*: + * Filter logs to spike timeframe + * Check for unusual request patterns + * Identify custom header (user ID, customer ID) if present + +*Common Causes*: +* Test suite running against prod gateway +* A/B test routing all traffic to expensive model +* User error (wrong model in config) +* Runaway loop in application code + +=== Task 5: "is provider x more reliable than provider y?" + +1. *Open Provider Comparison Dashboard* +2. *Compare Error Rates*: + +| Provider | Requests | Error Rate | Fallback Triggers | +|----------|----------|------------|-------------------| +| OpenAI | 500K | 0.8% | 50 (rate limits) | +| Anthropic | 300K | 0.3% | 5 (timeouts) | + +*Insight*: Anthropic has 62% lower error rate + +3. *Compare Latencies*: + +| Provider | p50 Latency | p99 Latency | +|----------|-------------|-------------| +| OpenAI | 1.0s | 3.5s | +| Anthropic | 0.8s | 2.5s | + +*Insight*: Anthropic is 20% faster at p50, 28% faster at p99 + +*Decision*: Prioritize Anthropic in routing pools + +== Metrics retention + +// PLACEHOLDER: Confirm metrics retention policy + +*Retention Period*: +* *High-resolution* (1-minute granularity): // PLACEHOLDER: e.g., 7 days +* *Medium-resolution* (1-hour granularity): // PLACEHOLDER: e.g., 30 days +* *Low-resolution* (1-day granularity): // PLACEHOLDER: e.g., 1 year + +*Note*: Aggregate metrics retained longer than individual request logs + +== API access to metrics + +// PLACEHOLDER: Document metrics API if available + +=== List available metrics + +[source,bash] +---- +curl https://{CLUSTER_ID}.cloud.redpanda.com/api/ai-gateway/metrics/list \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" +---- + + +*Response*: +[source,json] +---- +{ + "metrics": [ + "requests", + "tokens.prompt", + "tokens.completion", + "tokens.total", + "cost.estimated", + "latency.p50", + "latency.p95", + "latency.p99", + "errors.rate", + "success.rate", + "fallback.rate" + ] +} +---- + + +=== Query specific metric + +[source,bash] +---- +curl https://{CLUSTER_ID}.cloud.redpanda.com/api/ai-gateway/metrics/query \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "metric": "requests", + "gateway_id": "gw_abc123", + "start_time": "2025-01-01T00:00:00Z", + "end_time": "2025-01-31T23:59:59Z", + "granularity": "1d", + "group_by": ["model"] + }' +---- + + +*Response*: +[source,json] +---- +{ + "metric": "requests", + "granularity": "1d", + "data": [ + { + "timestamp": "2025-01-01T00:00:00Z", + "model": "openai/gpt-4o", + "value": 10000 + }, + { + "timestamp": "2025-01-01T00:00:00Z", + "model": "anthropic/claude-sonnet-3.5", + "value": 5000 + }, + ... + ] +} +---- + + +== Best practices + +=== 1. set up budget alerts early +* Don't wait for surprise bills +* Alert at 50%, 80%, 90% of budget +* Include multiple stakeholders (eng, finance) + +=== 2. create team dashboards +* One dashboard per team showing their gateway(s) +* Empowers teams to self-optimize +* Reduces central ops burden + +=== 3. monitor fallback rate +* Low fallback rate (0-5%): Normal, failover working +* High fallback rate (>20%): Investigate primary provider issues +* Zero fallback rate: Verify fallback config is correct + +=== 4. compare models regularly +* Run A/B tests with metrics +* Reassess as pricing and models change +* Don't assume expensive = better quality for your use case + +=== 5. track trends, not point-in-time +* Day-to-day variance is normal +* Look for week-over-week and month-over-month trends +* Seasonal patterns (e.g., more usage on weekdays) + +== Troubleshoot metrics issues + +=== Issue: "metrics don't match my provider invoice" + +*Possible Causes*: +1. Metrics are estimates based on public pricing +2. Your contract has custom pricing +3. Provider changed pricing mid-month + +*Solution*: +* Use metrics for trends and optimization decisions +* Use provider invoices for actual billing +* // PLACEHOLDER: Can users configure custom pricing in gateway? + +=== Issue: "metrics are delayed or missing" + +*Possible Causes*: +1. Metrics aggregation has delay (// PLACEHOLDER: typical delay?) +2. Time range outside retention period +3. No requests in selected time range (empty data) + +*Solution*: +1. Wait and refresh (// PLACEHOLDER: Xminutes typical delay) +2. Check retention policy +3. Verify requests were sent (check logs) + +=== Issue: "dashboard shows 'no data'" + +*Possible Causes*: +1. Filters too restrictive (no matching requests) +2. Gateway has no traffic yet +3. Permissions issue (can't access this gateway's metrics) + +*Solution*: +1. Remove filters, widen time range +2. Send test request (see [Quickstart](// PLACEHOLDER: link)) +3. Check permissions with admin + +== Next steps + +* *View Individual Requests* → [Observability: Logs](// PLACEHOLDER: link) +* *Set Up Alerts* → [Alerting Guide](// PLACEHOLDER: link) +* *Optimize Costs* → [Cost Optimization Guide](// PLACEHOLDER: link) +* *Export to BI Tools* → [Data Export Guide](// PLACEHOLDER: link) +* *Compare Models* → [Model Selection Guide](// PLACEHOLDER: link) + +== Related pages + +* [Observability: Logs](// PLACEHOLDER: link) +* [Budget & Rate Limits](// PLACEHOLDER: link) +* [Cost Optimization](// PLACEHOLDER: link) +* [Performance Benchmarks](// PLACEHOLDER: link) diff --git a/modules/ai-agents/partials/quickstart-enhanced.adoc b/modules/ai-agents/partials/quickstart-enhanced.adoc new file mode 100644 index 000000000..f0ae04cca --- /dev/null +++ b/modules/ai-agents/partials/quickstart-enhanced.adoc @@ -0,0 +1,503 @@ += AI gateway quickstart + +Get your first request routed through Redpanda AI Gateway in under 10 minutes. + +== What you'll accomplish + +✓ *2 minutes*: Route your first LLM request through the gateway +✓ *5 minutes*: See observability data in the dashboard +✓ *7 minutes*: Add a fallback provider for reliability +✓ *10 minutes*: Write your first CEL routing rule + +*Total time*: 10-15 minutes + +== Prerequisites + +Before starting, ensure you have: +* ✅ Redpanda Cloud account with BYOC // PLACEHOLDER: specific version? +* ✅ Admin access to configure providers and gateways +* ✅ API keys for at least one LLM provider (OpenAI, Anthropic, etc.) +* ✅ Python 3.8+ or Node.js 18+ (for examples) + +== Step 1: configure a provider (admin task) + +*Time: ~2 minutes* + +Providers must be configured before they can be used in gateways. + +// PLACEHOLDER: Add UI navigation path, e.g., "Console → AI Gateway → Providers → Add Provider" + +1. *Navigate to Providers*: + * Open Redpanda Cloud Console + * Go to // PLACEHOLDER: exact menu path + +2. *Add Provider*: + ``` + Provider: OpenAI + API Key: sk-... + Enabled Models: gpt-4o, gpt-4o-mini + ``` + + // PLACEHOLDER: Add screenshot of provider configuration form + +3. *Verify*: + * Provider status shows "Active" + * Models appear in model catalog + +*Alternative: CLI* (if available) +[source,bash] +---- +# PLACEHOLDER: CLI command for adding provider +rpk cloud ai-gateway provider create \ + --provider openai \ + --api-key sk-... \ + --models gpt-4o,gpt-4o-mini +---- + + +*Supported Providers*: +// PLACEHOLDER: List currently supported providers +* OpenAI +* Anthropic +* // PLACEHOLDER: Others? + +See link:// PLACEHOLDER: link[Admin Guide: Providers] for detailed configuration options. + +== Step 2: create a gateway + +*Time: ~1 minute* + +Gateways define routing policies, rate limits, and observability scope. + +// PLACEHOLDER: Add UI navigation path + +1. *Navigate to Gateways*: + * Go to // PLACEHOLDER: exact menu path + +2. *Create Gateway*: + ``` + Name: my-first-gateway + Workspace: default + Description: Quickstart gateway for testing + ``` + + // PLACEHOLDER: Add screenshot of gateway creation form + +3. *Save Gateway ID*: + After creation, copy your gateway ID (required for requests): + ``` + Gateway ID: gw_abc123... + Gateway Endpoint: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1 + ``` + + // PLACEHOLDER: Confirm exact endpoint format + +*Recommended Gateway Patterns*: +* One gateway per environment (staging, production) +* One gateway per team (for budget isolation) +* One gateway per customer (for multi-tenant SaaS) + +See link:// PLACEHOLDER: link[Gateway Creation Guide] for best practices. + +== Step 3: send your first request + +*Time: ~2 minutes* + +Now route a request through your gateway. + +=== Python + +[source,python] +---- +from openai import OpenAI +import os + +# Configure client to use AI Gateway +client = OpenAI( + base_url="https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1", # Gateway endpoint + api_key=os.getenv("REDPANDA_CLOUD_TOKEN"), # Your Redpanda Cloud token + default_headers={ + "rp-aigw-id": "gw_abc123..." # Your gateway ID from Step 2 + } +) + +# Make a request (note the vendor/model_id format) +response = client.chat.completions.create( + model="openai/gpt-4o-mini", # Format: {provider}/{model} + messages=[ + {"role": "user", "content": "Say 'Hello from AI Gateway!'"} + ], + max_tokens=20 +) + +print(response.choices[0].message.content) +# Output: Hello from AI Gateway! +---- + + +=== Typescript/javascript + +[source,typescript] +---- +import OpenAI from 'openai'; + +const client = new OpenAI({ + baseURL: 'https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1', + apiKey: process.env.REDPANDA_CLOUD_TOKEN, + defaultHeaders: { + 'rp-aigw-id': 'gw_abc123...' + } +}); + +const response = await client.chat.completions.create({ + model: 'openai/gpt-4o-mini', + messages: [ + { role: 'user', content: 'Say "Hello from AI Gateway!"' } + ], + max_tokens: 20 +}); + +console.log(response.choices[0].message.content); +// Output: Hello from AI Gateway! +---- + + +=== Curl + +[source,bash] +---- +curl https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: gw_abc123..." \ + -d '{ + "model": "openai/gpt-4o-mini", + "messages": [ + {"role": "user", "content": "Say \"Hello from AI Gateway!\""} + ], + "max_tokens": 20 + }' +---- + + +*Expected Response*: +[source,json] +---- +{ + "id": "chatcmpl-...", + "object": "chat.completion", + "created": 1704844800, + "model": "openai/gpt-4o-mini", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello from AI Gateway!" + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 8, + "completion_tokens": 5, + "total_tokens": 13 + } +} +---- + + +*Troubleshooting*: +* `401 Unauthorized` → Check `REDPANDA_CLOUD_TOKEN` +* `404 Not Found` → Verify `base_url` is correct +* `Model not found` → Ensure model is enabled in Step 1 +* `Missing rp-aigw-id` → Verify header is set + +See link:// PLACEHOLDER: link[Troubleshooting Guide] for more help. + +== Step 4: verify in observability dashboard + +*Time: ~1 minute* + +Confirm your request appears in the AI Gateway dashboard. + +// PLACEHOLDER: Add UI navigation path and screenshots + +1. *Navigate to Logs*: + * Go to // PLACEHOLDER: Console → AI Gateway → {Gateway Name} → Logs + +2. *Find Your Request*: + * Filter by Gateway: `my-first-gateway` + * Filter by Model: `openai/gpt-4o-mini` + * Time range: Last 5 minutes + +3. *Verify Fields*: + * ✅ Model: `openai/gpt-4o-mini` + * ✅ Provider: OpenAI + * ✅ Status: 200 + * ✅ Prompt tokens: ~8 + * ✅ Completion tokens: ~5 + * ✅ Estimated cost: // PLACEHOLDER: $X.XXXX + * ✅ Latency: // PLACEHOLDER: ~XXXms + +4. *Click Request to Expand*: + * View full prompt and response + * See request headers + * Check routing decision (which provider pool was used) + +*If request doesn't appear*: +* Wait // PLACEHOLDER: Xs (logs may have delay) +* Check gateway ID matches +* Verify request succeeded (no error in client) +* See link:// PLACEHOLDER: link[End-to-End Validation Guide] + +*🎉 Congratulations!* You've successfully routed your first request through AI Gateway and verified observability. + +''' + +== Next steps: add failover (optional, +3 minutes) + +Add automatic failover to a backup provider for reliability. + +=== Step 5: add second provider + +*Time: ~1 minute* + +Add Anthropic as a fallback option: + +// PLACEHOLDER: Add UI path + +1. *Navigate to Providers* → *Add Provider*: + ``` + Provider: Anthropic + API Key: sk-ant-... + Enabled Models: claude-sonnet-3.5 + ``` + +2. *Verify*: + * Anthropic provider status: Active + * Models appear in catalog + +=== Step 6: configure provider pool with fallback + +*Time: ~2 minutes* + +Update your gateway to use OpenAI as primary, Anthropic as fallback. + +// PLACEHOLDER: Add UI path and configuration format + +1. *Navigate to Gateway Settings*: + * Go to // PLACEHOLDER: AI Gateway → {Gateway Name} → Routing + +2. *Configure Provider Pool*: + ```yaml + # PLACEHOLDER: Confirm actual configuration format + routing: + primary_pool: + * provider: openai + models: [gpt-4o, gpt-4o-mini] + fallback_pool: + * provider: anthropic + models: [claude-sonnet-3.5] + + fallback_triggers: + * rate_limit_exceeded + * timeout + * 5xx_errors + ``` + + // PLACEHOLDER: Add screenshot of routing configuration + +3. *Save Configuration* + +=== Step 7: test failover + +*Time: ~1 minute* + +Simulate a provider failure to see fallback in action. + +// PLACEHOLDER: Add method to test failback, or skip if not easily testable + +*Option A: Disable Primary Provider Temporarily* +1. Disable OpenAI provider in settings +2. Send request with `openai/gpt-4o` model +3. Gateway should automatically route to Anthropic fallback +4. Check logs to confirm fallback was used + +*Option B: Trigger Rate Limit* +1. Send many requests rapidly to hit rate limit +2. Gateway should fallback to Anthropic +3. Check logs for "fallback_triggered" indicator + +*Verify Fallback*: +[source,python] +---- +response = client.chat.completions.create( + model="openai/gpt-4o", # Request OpenAI model + messages=[{"role": "user", "content": "Test fallback"}] +) + +# Check which provider actually handled it +# PLACEHOLDER: How to verify this - response header? Log metadata? +---- + + +*Check Dashboard*: +* Request should show: + * Requested model: `openai/gpt-4o` + * Actual provider: Anthropic (fallback) + * Fallback reason: // PLACEHOLDER: rate_limit / timeout / error + +''' + +== Next steps: add routing rule (optional, +3 minutes) + +Use CEL expressions to route requests based on headers or content. + +=== Step 8: create CEL routing rule + +*Time: ~2 minutes* + +Route premium users to better models automatically. + +// PLACEHOLDER: Add UI path for CEL configuration + +1. *Navigate to Gateway Settings*: + * Go to // PLACEHOLDER: AI Gateway → {Gateway Name} → Routing Rules + +2. *Add CEL Rule*: + ```cel + # Route based on user tier header + request.headers["x-user-tier"] == "premium" + ? "openai/gpt-4o" + : "openai/gpt-4o-mini" + ``` + + // PLACEHOLDER: Add screenshot of CEL editor with syntax highlighting + +3. *Test Rule* (if UI supports testing): + * Input test headers: `x-user-tier: premium` + * Verify output: `openai/gpt-4o` + * Input test headers: `x-user-tier: free` + * Verify output: `openai/gpt-4o-mini` + +4. *Save Rule* + +=== Step 9: test routing rule + +*Time: ~1 minute* + +Send requests with different headers and verify routing. + +*Premium User Request*: +[source,python] +---- +response = client.chat.completions.create( + model="auto", # PLACEHOLDER: or how to trigger CEL routing + messages=[{"role": "user", "content": "Hello"}], + extra_headers={"x-user-tier": "premium"} +) + +# Should route to gpt-4o (premium model) +---- + + +*Free User Request*: +[source,python] +---- +response = client.chat.completions.create( + model="auto", + messages=[{"role": "user", "content": "Hello"}], + extra_headers={"x-user-tier": "free"} +) + +# Should route to gpt-4o-mini (cost-effective model) +---- + + +*Verify in Dashboard*: +* Check request logs +* Confirm correct model was selected based on header +* View routing decision explanation + +*🎉 Congratulations!* You've configured intelligent routing based on request context. + +''' + +== What you've learned + +✅ *Provider Configuration*: Added LLM providers (OpenAI, Anthropic) +✅ *Gateway Creation*: Created your first gateway with policies +✅ *Request Routing*: Sent requests through the gateway +✅ *Observability*: Verified requests in dashboard logs +✅ *Failover*: Configured automatic fallback to backup provider (optional) +✅ *Smart Routing*: Created CEL rule for dynamic model selection (optional) + +== What's next? + +=== Immediate next steps +1. *Set Rate Limits* → link:// PLACEHOLDER: link[Rate Limiting Guide] + * Protect against runaway costs + * Prevent abuse + +2. *Add Spend Limits* → link:// PLACEHOLDER: link[Budget Controls Guide] + * Set monthly budgets per gateway + * Get alerts before limits are hit + +3. *Configure MCP Aggregation* → link:// PLACEHOLDER: link[MCP Guide] + * Give agents access to tools + * Reduce token costs with deferred loading + +=== Explore advanced features +* *A/B Testing Models* → link:// PLACEHOLDER: link[A/B Testing Guide] +* *Multi-Tenancy Patterns* → link:// PLACEHOLDER: link[Multi-Tenancy Guide] +* *Cost Optimization* → link:// PLACEHOLDER: link[Cost Optimization Guide] +* *Performance Tuning* → link:// PLACEHOLDER: link[Performance Guide] + +=== Integration guides +* link:// PLACEHOLDER: link[OpenAI SDK Integration] +* link:// PLACEHOLDER: link[Anthropic SDK Integration] +* link:// PLACEHOLDER: link[LangChain Integration] +* link:// PLACEHOLDER: link[LlamaIndex Integration] +* link:// PLACEHOLDER: link[Claude Code CLI] +* link:// PLACEHOLDER: link[VS Code Extension] +* link:// PLACEHOLDER: link[Cursor IDE] + +=== Migrate existing applications +* link:// PLACEHOLDER: link[Migration Guide: From Direct Integration to Gateway] + +== Common next questions + +*Q: How do I switch between providers without code changes?* +A: Change the model string in your gateway routing rules. No code deployment needed. + +*Q: How much latency does the gateway add?* +A: Typically // PLACEHOLDER: Xms overhead. See link:// PLACEHOLDER: link[Performance Benchmarks]. + +*Q: Can I use the same gateway for multiple applications?* +A: Yes, but we recommend separate gateways per environment or team for better cost tracking. + +*Q: How do I attribute costs to specific customers?* +A: Use CEL routing with custom headers, then filter logs by header value. See link:// PLACEHOLDER: link[Cost Attribution Guide]. + +*Q: Does the gateway work with streaming responses?* +A: // PLACEHOLDER: Yes/No, with any limitations + +*Q: What happens if the gateway goes down?* +A: // PLACEHOLDER: Describe high availability setup, or recommend keeping fallback to direct integration + +== Get help + +* *Documentation*: link:// PLACEHOLDER: actual URL[https://docs.redpanda.com/redpanda-cloud/ai-gateway/] +* *Troubleshooting*: link:// PLACEHOLDER: link[Common Issues Guide] +* *Community*: // PLACEHOLDER: Slack, Discord, forum link +* *Support*: // PLACEHOLDER: support email or portal + +''' + +*Related Pages*: +* link:// PLACEHOLDER: link[What is AI Gateway?] +* link:// PLACEHOLDER: link[Core Concepts] +* link:// PLACEHOLDER: link[End-to-End Validation] +* link:// PLACEHOLDER: link[CEL Routing Deep Dive] +* link:// PLACEHOLDER: link[Observability: Logs & Metrics] diff --git a/modules/ai-agents/partials/what-is-ai-gateway.adoc b/modules/ai-agents/partials/what-is-ai-gateway.adoc new file mode 100644 index 000000000..a98e42945 --- /dev/null +++ b/modules/ai-agents/partials/what-is-ai-gateway.adoc @@ -0,0 +1,419 @@ += What is Redpanda AI gateway? + +== Overview + +Redpanda AI Gateway is a unified access layer for LLM providers and AI tools that sits between your applications and the AI services they use. It provides centralized routing, policy enforcement, cost management, and observability for all your AI traffic. + +== The problem + +Modern AI applications face several critical challenges: + +=== 1. provider fragmentation + +* Applications hardcode provider-specific SDKs (OpenAI, Anthropic, Google, etc.) +* Switching providers requires code changes and redeployment +* Testing across providers is time-consuming and error-prone +* Provider outages directly impact your application + +=== 2. cost spirals without visibility + +* No centralized view of token usage across teams and applications +* Difficult to attribute costs to specific customers, features, or environments +* Testing and debugging can rack up unexpected bills +* No way to enforce budgets or rate limits per team/customer + +=== 3. tool coordination complexity + +* Agents need access to multiple MCP (Model Context Protocol) servers +* Managing tool discovery and execution is repetitive across projects +* High token costs from loading all available tools upfront +* No centralized governance over which tools agents can access + +=== 4. observability gaps + +* Requests scattered across multiple provider dashboards +* Can't reconstruct user sessions that span multiple models +* No unified view of latency, errors, and costs +* Debugging "the AI gave the wrong answer" requires manual log diving + +== What AI gateway solves + +Redpanda AI Gateway addresses these challenges through four core capabilities: + +=== 1. unified LLM access (single endpoint for all providers) + +// PLACEHOLDER: Add architecture diagram showing: +// - Application → AI Gateway → Multiple LLM Providers (OpenAI, Anthropic, etc.) +// - Single baseURL configuration +// - Model routing via vendor/model_id format + +*Before (Direct Integration)* + +[source,python] +---- +# OpenAI +from openai import OpenAI +client = OpenAI(api_key="sk-...") +response = client.chat.completions.create( + model="gpt-4o", + messages=[{"role": "user", "content": "Hello"}] +) + +# Anthropic (different SDK, different patterns) +from anthropic import Anthropic +client = Anthropic(api_key="sk-ant-...") +response = client.messages.create( + model="claude-sonnet-3.5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}] +) +---- + +*After (AI Gateway - OpenAI-Compatible)* + +[source,python] +---- +from openai import OpenAI + +# Single configuration, multiple providers +client = OpenAI( + base_url="https://{GATEWAY_ENDPOINT}", + api_key="your-redpanda-token", + default_headers={"rp-aigw-id": "{GATEWAY_ID}"} +) + +# Route to OpenAI +response = client.chat.completions.create( + model="openai/gpt-4o", + messages=[{"role": "user", "content": "Hello"}] +) + +# Route to Anthropic (same code, different model string) +response = client.chat.completions.create( + model="anthropic/claude-sonnet-3.5", + messages=[{"role": "user", "content": "Hello"}] +) +---- + +*Result*: Change `model` parameter to switch providers. No code redeployment needed. + +=== 2. policy-based routing & cost control + +Define routing rules, rate limits, and budgets once; enforce them automatically: + +*Example: Tier-Based Routing* + +[source,cel] +---- +// Route premium users to best model, free users to cost-effective model +request.headers["x-user-tier"] == "premium" + ? "anthropic/claude-opus-4" + : "anthropic/claude-sonnet-3.5" +---- + +*Example: Environment-Based Budget* + +// PLACEHOLDER: Confirm exact policy configuration format + +[source,yaml] +---- +rate_limits: + staging: 100 requests/minute + production: 10000 requests/minute + +spend_limits: + staging: $500/month + production: $50000/month +---- + +*Example: Automatic Failover* + +// PLACEHOLDER: Add details on pool configuration and failback behavior + +* Primary: OpenAI GPT-4 +* Fallback: Anthropic Claude Opus on rate limits or timeouts +* Result: 99.9% uptime even during provider outages + +=== 3. MCP aggregation & orchestration + +*Agent Tool Access Without the Overhead* + +*Before*: Agent loads all tools from multiple MCP servers upfront + +* Sends 50+ tool definitions with every request +* High token costs (thousands of tokens per request) +* Slow agent startup +* No centralized governance + +*After*: AI Gateway aggregates MCP servers + +* Deferred tool loading: Only search + orchestrator tools loaded initially +* *80-90% token reduction* depending on configuration +* Agent queries for specific tools only when needed +* Centralized approval of MCP servers + +*Orchestrator for Complex Workflows* + +* Single JavaScript-based orchestrator tool +* Reduces multi-step workflows from multiple round trips to one call +* Example: "Search vector DB → if results insufficient → fallback to web search" + +// PLACEHOLDER: Add link to MCP aggregation guide when ready + +=== 4. unified observability & cost tracking + +*Single Dashboard for All LLM Traffic* + +// PLACEHOLDER: Add screenshots of: +// - Request logs view +// - Cost breakdown by model/provider +// - Latency histogram +// - Error rate tracking + +Track across all requests: + +* Volume (requests per gateway, model, provider) +* Token usage (prompt + completion tokens) +* Estimated spend (per model, with cross-provider comparison) +* Latency (p50, p95, p99) +* Errors (by type, provider, model) + +*Use Cases*: + +* "Which model is the most cost-effective for our use case?" +* "Why did this specific user request fail?" +* "How much does our staging environment cost us per week?" +* "What's the latency difference between OpenAI and Anthropic for our workload?" + +== Cost comparison example + +// PLACEHOLDER: Insert real customer data or anonymized case study + +*Scenario*: SaaS chatbot with 1M requests/month, averaging 500 prompt + 300 completion tokens + +[cols="1,1,2"] +|=== +|Configuration |Monthly Cost |Notes + +|*Direct Integration* (no gateway) +|// PLACEHOLDER: $X,XXX +|No caching, no routing optimization + +|*+ AI Gateway* (basic routing) +|// PLACEHOLDER: $X,XXX +|Provider failover, unified observability + +|*+ Caching* +|// PLACEHOLDER: $X,XXX +|// PLACEHOLDER: X% reduction from cache hits + +|*+ Deferred Tool Loading* +|// PLACEHOLDER: $X,XXX +|80-90% token reduction for agent workloads + +|*+ Tier-Based Routing* +|// PLACEHOLDER: $X,XXX +|Premium users → better model, free → cost-effective +|=== + +*Total Savings*: // PLACEHOLDER: $X,XXX/month (XX% reduction) + +*Hidden Savings*: + +* Developer time: No more managing multiple provider SDKs +* Incident response: Automatic failover reduces downtime costs +* Experimentation: Safe A/B testing without risking production + +== Common gateway patterns + +=== Pattern 1: team isolation + +*Use Case*: Multiple teams sharing infrastructure, need separate budgets and policies + +*Setup*: Create one gateway per team + +* Team A Gateway: $5K/month budget, staging + production environments +* Team B Gateway: $10K/month budget, different rate limits +* Each team sees only their traffic in observability dashboards + +// PLACEHOLDER: Link to multi-tenancy guide + +=== Pattern 2: environment separation + +*Use Case*: Prevent staging traffic from affecting production metrics + +*Setup*: Separate gateways for staging vs production + +* Staging Gateway: Lower rate limits, restricted model access, aggressive cost controls +* Production Gateway: High rate limits, all models enabled, alerting on anomalies + +=== Pattern 3: primary + fallback for reliability + +*Use Case*: Ensure uptime during provider outages + +*Setup*: Configure provider pools with automatic failover + +* Primary: OpenAI (preferred for quality) +* Fallback: Anthropic (activates on OpenAI rate limits or timeouts) +* Monitor fallback rate to detect primary provider issues early + +=== Pattern 4: a/b testing models + +*Use Case*: Compare model quality/cost without dual integration + +*Setup*: Route percentage of traffic to different models + +// PLACEHOLDER: Confirm if percentage-based routing is supported, or if it's header-based only + +* 80% traffic → claude-sonnet-3.5 +* 20% traffic → claude-opus-4 +* Compare quality metrics and costs, then adjust + +=== Pattern 5: customer-based routing + +*Use Case*: SaaS product with tiered pricing (free, pro, enterprise) + +*Setup*: CEL routing based on request headers + +[source,cel] +---- +request.headers["x-customer-tier"] == "enterprise" ? "anthropic/claude-opus-4" : +request.headers["x-customer-tier"] == "pro" ? "anthropic/claude-sonnet-3.5" : +"anthropic/claude-haiku" +---- + +== Deployment model + +// PLACEHOLDER: Verify BYOC availability and any managed offering plans + +*BYOC (Bring Your Own Cloud)* + +* Currently available: BYOC version for // PLACEHOLDER: specific Redpanda version +* Deployment: Within your Redpanda Cloud cluster +* Data residency: All traffic stays in your cloud account +* Supported clouds: // PLACEHOLDER: AWS, GCP, Azure? + +// PLACEHOLDER: If managed offering is planned, add: +// *Managed (Redpanda Cloud)* +// - Coming soon: Fully managed AI Gateway +// - No infrastructure management +// - Global deployment regions +// - Uptime SLA + +== What's supported today + +=== LLM providers + +// PLACEHOLDER: Confirm currently supported providers + +* OpenAI +* Anthropic +* // PLACEHOLDER: Google, AWS Bedrock, Azure OpenAI, others? + +=== API compatibility + +* OpenAI-compatible `/v1/chat/completions` endpoint +* // PLACEHOLDER: Streaming support? +* // PLACEHOLDER: Embeddings support? +* // PLACEHOLDER: Other endpoints? + +=== Policy features + +* CEL-based routing expressions +* Rate limiting (// PLACEHOLDER: per-gateway, per-header, per-tenant?) +* Monthly spend limits (// PLACEHOLDER: per-gateway, per-workspace?) +* Provider pools with automatic failover +* // PLACEHOLDER: Caching support? + +=== MCP support + +* MCP server aggregation +* Deferred tool loading (80-90% token reduction) +* JavaScript orchestrator for multi-step workflows +* // PLACEHOLDER: Tool execution sandboxing? + +=== Observability + +* Request logs with full prompt/response history +* Token usage tracking +* Estimated cost per request +* Latency metrics +* // PLACEHOLDER: Metrics export? OpenTelemetry support? + +== What's not supported yet + +// PLACEHOLDER: List current limitations, for example: +// - Custom model deployments (Azure OpenAI BYOK, AWS Bedrock custom models) +// - Response caching +// - Prompt templates/versioning +// - Guardrails (PII detection, content moderation) +// - Multi-region active-active deployment +// - Metrics export to external systems +// - Budget alerts/notifications + +== Architecture + +// PLACEHOLDER: Add architecture diagram showing: +// 1. Control Plane: +// - Workspace management +// - Provider/model configuration +// - Gateway creation and policy definition +// - Admin console +// +// 2. Data Plane: +// - Request ingestion +// - Policy evaluation (rate limits → spend limits → routing → execution) +// - Provider pool selection and failover +// - MCP aggregation layer +// - Response logging and metrics +// +// 3. Observability Plane: +// - Request logs storage +// - Metrics aggregation +// - Dashboard UI + +*Request Lifecycle*: + +. Application sends request to gateway endpoint with `rp-aigw-id` header +. Gateway authenticates request +. Rate limit policy evaluates (allow/deny) +. Spend limit policy evaluates (allow/deny) +. Routing policy evaluates (which model/provider to use) +. Provider pool selects backend (primary/fallback) +. Request forwarded to LLM provider +. Response returned to application +. Request logged with tokens, cost, latency, status + +*MCP Request Lifecycle*: + +. Application discovers tools via `/mcp` endpoint +. Gateway aggregates tools from approved MCP servers +. Application receives search + orchestrator tools (deferred loading) +. Application invokes specific tool +. Gateway routes to appropriate MCP server +. Tool execution result returned +. Request logged with execution time, status + +== Next steps + +* *New to AI Gateway?* → link:quickstart.adoc[Quickstart: Route your first request] +* *Understand the concepts* → link:core-concepts.adoc[Core Concepts: Gateways, Providers, Models] +* *Set up providers* → link:admin-guide-providers.adoc[Admin Guide: Configure LLM Providers] +* *Define policies* → link:routing-rate-limits.adoc[Routing & Rate Limits Guide] +* *Integrate agents* → link:mcp-aggregation-guide.adoc[MCP Aggregation Guide] +* *Monitor usage* → link:observability-logs.adoc[Observability: Logs and Metrics] + +== Get help + +* Documentation: https://docs.redpanda.com/redpanda-cloud/ai-gateway/ +* Community: // PLACEHOLDER: Slack, Discord, or forum link +* Support: // PLACEHOLDER: support email or portal + +''' + +*Related Pages*: + +* link:quickstart.adoc[Quickstart] +* link:core-concepts.adoc[Core Concepts] +* link:architecture-deep-dive.adoc[Architecture Deep Dive] +* link:use-cases-patterns.adoc[Use Cases & Patterns] From f2c87a32799c8c5c08504bfdc02c82dc6c3d6915 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Sun, 11 Jan 2026 20:03:17 -0700 Subject: [PATCH 09/97] clean up cc drafts --- modules/ROOT/nav.adoc | 10 +- .../pages/{ => ai-gateway}/ai-gateway.adoc | 4 +- .../ai-gateway}/cel-routing-cookbook.adoc | 332 ++++++++++-------- modules/ai-agents/pages/ai-gateway/index.adoc | 3 + .../ai-gateway}/mcp-aggregation-guide.adoc | 313 ++++++++++------- .../ai-gateway}/migration-guide.adoc | 250 +++++++------ .../ai-gateway}/observability-logs.adoc | 156 ++++---- .../ai-gateway}/observability-metrics.adoc | 320 ++++++++++------- .../ai-gateway}/quickstart-enhanced.adoc | 189 +++++----- .../ai-gateway}/what-is-ai-gateway.adoc | 105 +++--- modules/ai-agents/pages/index.adoc | 3 - 11 files changed, 968 insertions(+), 717 deletions(-) rename modules/ai-agents/pages/{ => ai-gateway}/ai-gateway.adoc (98%) rename modules/ai-agents/{partials => pages/ai-gateway}/cel-routing-cookbook.adoc (76%) create mode 100644 modules/ai-agents/pages/ai-gateway/index.adoc rename modules/ai-agents/{partials => pages/ai-gateway}/mcp-aggregation-guide.adoc (83%) rename modules/ai-agents/{partials => pages/ai-gateway}/migration-guide.adoc (83%) rename modules/ai-agents/{partials => pages/ai-gateway}/observability-logs.adoc (85%) rename modules/ai-agents/{partials => pages/ai-gateway}/observability-metrics.adoc (74%) rename modules/ai-agents/{partials => pages/ai-gateway}/quickstart-enhanced.adoc (75%) rename modules/ai-agents/{partials => pages/ai-gateway}/what-is-ai-gateway.adoc (82%) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 2e268d196..f64c40349 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -70,7 +70,15 @@ ** xref:security:cloud-safety-reliability.adoc[Safety and Reliability] * xref:ai-agents:index.adoc[AI Agents] -** xref:ai-agents:ai-gateway.adoc[] +** xref:ai-agents:ai-gateway/index.adoc[AI Gateway] +*** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[] +*** xref:ai-agents:ai-gateway/ai-gateway.adoc[] +*** xref:ai-agents:ai-gateway/quickstart-enhanced.adoc[] +*** xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[] +*** xref:ai-agents:ai-gateway/observability-logs.adoc[] +*** xref:ai-agents:ai-gateway/observability-metrics.adoc[] +*** xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[] +*** xref:ai-agents:ai-gateway/migration-guide.adoc[] ** xref:ai-agents:mcp/overview.adoc[MCP Overview] ** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] *** xref:ai-agents:mcp/local/overview.adoc[Overview] diff --git a/modules/ai-agents/pages/ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway/ai-gateway.adoc similarity index 98% rename from modules/ai-agents/pages/ai-gateway.adoc rename to modules/ai-agents/pages/ai-gateway/ai-gateway.adoc index 2e327da98..a91980418 100644 --- a/modules/ai-agents/pages/ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway/ai-gateway.adoc @@ -1,5 +1,5 @@ = AI Gateway Quickstart -:description: Learn how to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. +:description: Quickstart to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. @@ -342,3 +342,5 @@ const openai = new OpenAI({ } }); ---- + +== Next steps \ No newline at end of file diff --git a/modules/ai-agents/partials/cel-routing-cookbook.adoc b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc similarity index 76% rename from modules/ai-agents/partials/cel-routing-cookbook.adoc rename to modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc index 44a505cfb..50bd6405c 100644 --- a/modules/ai-agents/partials/cel-routing-cookbook.adoc +++ b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc @@ -1,10 +1,12 @@ -= CEL routing: deep dive & cookbook += DRAFT: CEL Routing Cookbook +:description: Quickstart to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. == Overview Redpanda AI Gateway uses CEL (Common Expression Language) for dynamic request routing. CEL expressions evaluate request properties (headers, body, context) and determine which model or provider should handle each request. -*CEL enables*: +CEL enables: + * User-based routing (free vs premium tiers) * Content-based routing (by prompt topic, length, complexity) * Environment-based routing (staging vs production models) @@ -15,19 +17,21 @@ Redpanda AI Gateway uses CEL (Common Expression Language) for dynamic request ro == CEL basics -=== What is cel? +=== What is CEL? CEL (Common Expression Language) is a non-Turing-complete expression language designed for fast, safe evaluation. It's used by Google (Firebase, Cloud IAM), Kubernetes, Envoy, and other systems. -*Key Properties*: -* *Safe*: Cannot loop infinitely or access system resources -* *Fast*: Evaluates in microseconds -* *Readable*: Similar to Python/JavaScript expressions -* *Type-safe*: Errors caught at configuration time, not runtime +Key properties: + +* Safe: Cannot loop infinitely or access system resources +* Fast: Evaluates in microseconds +* Readable: Similar to Python/JavaScript expressions +* Type-safe: Errors caught at configuration time, not runtime === CEL syntax primer -*Comparison Operators*: +Comparison operators: + [source,cel] ---- == // equal @@ -39,7 +43,8 @@ CEL (Common Expression Language) is a non-Turing-complete expression language de ---- -*Logical Operators*: +Logical operators: + [source,cel] ---- && // AND @@ -48,14 +53,16 @@ CEL (Common Expression Language) is a non-Turing-complete expression language de ---- -*Ternary Operator* (most common pattern): +Ternary operator (most common pattern): + [source,cel] ---- condition ? value_if_true : value_if_false ---- -*Functions*: +Functions: + [source,cel] ---- .size() // Length of string or array @@ -67,7 +74,8 @@ has(field) // Check if field exists ---- -*Examples*: +Examples: + [source,cel] ---- // Simple comparison @@ -106,7 +114,7 @@ request.headers["x-request-id"] // Standard header ---- -*Note*: Header names are case-insensitive in HTTP, but CEL requires lowercase keys. +NOTE: Header names are case-insensitive in HTTP, but CEL requires lowercase keys. === `request.body` (object) @@ -125,7 +133,8 @@ request.body.stream // Bool: Streaming enabled (if set) ---- -*Note*: Fields are optional. Use `has()` to check existence: +NOTE: Fields are optional. Use `has()` to check existence: + [source,cel] ---- has(request.body.max_tokens) ? request.body.max_tokens : 1000 @@ -158,19 +167,19 @@ request.method == "POST" == CEL routing patterns Each pattern follows this structure: -* *When to use*: Scenario description -* *Expression*: CEL code -* *What happens*: Routing behavior -* *Verify*: How to test -* *Cost/performance impact*: Implications -''' +* When to use: Scenario description +* Expression: CEL code +* What happens: Routing behavior +* Verify: How to test +* Cost/performance impact: Implications -=== Pattern 1: tier-based routing +=== Pattern 1: Tier-based routing -*When to use*: Different user tiers (free, pro, enterprise) should get different model quality +When to use: Different user tiers (free, pro, enterprise) should get different model quality + +Expression: -*Expression*: [source,cel] ---- request.headers["x-user-tier"] == "enterprise" ? "openai/gpt-4o" : @@ -179,12 +188,14 @@ request.headers["x-user-tier"] == "pro" ? "anthropic/claude-sonnet-3.5" : ---- -*What happens*: +What happens: + * Enterprise users → GPT-4o (best quality) * Pro users → Claude Sonnet 3.5 (balanced) * Free users → GPT-4o-mini (cost-effective) -*Verify*: +Verify: + [source,python] ---- # Test enterprise @@ -205,20 +216,20 @@ response = client.chat.completions.create( ---- -*Cost Impact*: +Cost impact: + * Enterprise: ~$5.00 per 1K requests * Pro: ~$3.50 per 1K requests * Free: ~$0.50 per 1K requests -*Use Case*: SaaS product with tiered pricing where model quality is a differentiator +Use case: SaaS product with tiered pricing where model quality is a differentiator -''' +=== Pattern 2: Environment-based routing -=== Pattern 2: environment-based routing +When to use: Prevent staging from using expensive models -*When to use*: Prevent staging from using expensive models +Expression: -*Expression*: [source,cel] ---- request.headers["x-environment"] == "production" @@ -227,11 +238,13 @@ request.headers["x-environment"] == "production" ---- -*What happens*: +What happens: + * Production → GPT-4o (best quality) * Staging/dev → GPT-4o-mini (10x cheaper) -*Verify*: +Verify: + [source,python] ---- # Set environment header @@ -244,22 +257,24 @@ response = client.chat.completions.create( ---- -*Cost Impact*: +Cost impact: + * Prevents staging from inflating costs * Example: Staging with 100K test requests/day * GPT-4o: $500/day ($15K/month) * GPT-4o-mini: $50/day ($1.5K/month) * *Savings: $13.5K/month* -*Use Case*: Protect against runaway staging costs +Use case: Protect against runaway staging costs ''' -=== Pattern 3: content-length guard rails +=== Pattern 3: Content-length guard rails -*When to use*: Block or downgrade long prompts to prevent cost spikes +When to use: Block or downgrade long prompts to prevent cost spikes + +Expression (Block): -*Expression (Block)*: [source,cel] ---- request.body.messages.size() > 10 || request.body.max_tokens > 4000 @@ -268,11 +283,12 @@ request.body.messages.size() > 10 || request.body.max_tokens > 4000 ---- -*What happens*: +What happens: * Requests with >10 messages or >4000 max_tokens → Rejected with 400 error * Normal requests → GPT-4o -*Expression (Downgrade)*: +Expression (Downgrade): + [source,cel] ---- request.body.messages.size() > 10 || request.body.max_tokens > 4000 @@ -281,11 +297,13 @@ request.body.messages.size() > 10 || request.body.max_tokens > 4000 ---- -*What happens*: +What happens: + * Long conversations → Downgraded to cheaper model * Short conversations → Premium model -*Verify*: +Verify: + [source,python] ---- # Test rejection @@ -306,19 +324,19 @@ response = client.chat.completions.create( ---- -*Cost Impact*: +Cost impact: + * Prevents unexpected bills from verbose prompts * Example: Block requests >10K tokens (would cost $0.15 each) -*Use Case*: Staging cost controls, prevent prompt injection attacks that inflate token usage +Use case: Staging cost controls, prevent prompt injection attacks that inflate token usage -''' +=== Pattern 4: Topic-based routing -=== Pattern 4: topic-based routing +When to use: Route different question types to specialized models -*When to use*: Route different question types to specialized models +Expression: -*Expression*: [source,cel] ---- request.body.messages[0].content.contains("code") || @@ -329,11 +347,13 @@ request.body.messages[0].content.contains("programming") ---- -*What happens*: +What happens: + * Coding questions → GPT-4o (optimized for code) * General questions → Claude Sonnet (better prose) -*Verify*: +Verify: + [source,python] ---- # Test code question @@ -352,19 +372,20 @@ response = client.chat.completions.create( ---- -*Cost Impact*: +Cost impact: + * Optimize model selection for task type * Could improve quality without increasing costs -*Use Case*: Multi-purpose chatbot with both coding and general queries +Use case: Multi-purpose chatbot with both coding and general queries -''' -=== Pattern 5: geographic/regional routing +=== Pattern 5: Geographic/regional routing + +When to use: Route by user region for compliance or latency optimization -*When to use*: Route by user region for compliance or latency optimization +Expression: -*Expression*: [source,cel] ---- request.headers["x-user-region"] == "eu" @@ -373,11 +394,13 @@ request.headers["x-user-region"] == "eu" ---- -*What happens*: +What happens: + * EU users → EU-region model (GDPR compliance) * Other users → Default region -*Verify*: +Verify: + [source,python] ---- response = client.chat.completions.create( @@ -389,17 +412,17 @@ response = client.chat.completions.create( ---- -*Cost Impact*: Neutral (same model, different region) +Cost impact: Neutral (same model, different region) -*Use Case*: GDPR compliance, data residency requirements +Use case: GDPR compliance, data residency requirements -''' -=== Pattern 6: customer-specific routing +=== Pattern 6: Customer-specific routing + +When to use: Different customers have different model access (enterprise features) -*When to use*: Different customers have different model access (enterprise features) +Expression: -*Expression*: [source,cel] ---- request.headers["x-customer-id"] == "customer_vip_123" @@ -408,11 +431,13 @@ request.headers["x-customer-id"] == "customer_vip_123" ---- -*What happens*: +What happens: + * VIP customer → Best model * Standard customers → Normal model -*Verify*: +Verify: + [source,python] ---- response = client.chat.completions.create( @@ -424,21 +449,22 @@ response = client.chat.completions.create( ---- -*Cost Impact*: +Cost impact: + * VIP: ~$7.50 per 1K requests * Standard: ~$3.50 per 1K requests -*Use Case*: Enterprise contracts with premium model access +Use case: Enterprise contracts with premium model access -''' === Pattern 7: a/b testing (percentage-based routing) -*When to use*: Test new models with a percentage of traffic +When to use: Test new models with a percentage of traffic // PLACEHOLDER: Confirm if CEL can access random functions or if A/B testing requires different mechanism -*Expression (if random is available)*: +Expression (if random is available): + [source,cel] ---- // PLACEHOLDER: Verify CEL random function availability @@ -448,7 +474,8 @@ random() < 0.10 ---- -*Alternative (Hash-Based)*: +Alternative (hash-based): + [source,cel] ---- // Use customer ID hash for stable routing @@ -458,11 +485,13 @@ hash(request.headers["x-customer-id"]) % 100 < 10 ---- -*What happens*: +What happens: + * 10% of requests → New model (Opus 4) * 90% of requests → Existing model (GPT-4o) -*Verify*: +Verify: + [source,python] ---- # Send 100 requests, count which model was used @@ -476,19 +505,19 @@ for i in range(100): ---- -*Cost Impact*: +Cost impact: + * Allows safe, incremental rollout of new models * Monitor quality/cost for new model before full adoption -*Use Case*: Evaluate new models in production with real traffic +Use case: Evaluate new models in production with real traffic -''' +=== Pattern 8: Complexity-based routing -=== Pattern 8: complexity-based routing +When to use: Route simple queries to cheap models, complex queries to expensive models -*When to use*: Route simple queries to cheap models, complex queries to expensive models +Expression: -*Expression*: [source,cel] ---- request.body.messages.size() == 1 && @@ -498,11 +527,13 @@ request.body.messages[0].content.size() < 100 ---- -*What happens*: +What happens: + * Single short message (<100 chars) → Cheap model * Multi-turn or long messages → Premium model -*Verify*: +Verify: + [source,python] ---- # Test simple @@ -525,21 +556,21 @@ response = client.chat.completions.create( ---- -*Cost Impact*: +Cost impact: + * Can reduce costs significantly if simple queries are common * Example: 50% of queries are simple, save 90% on those = 45% total savings -*Use Case*: FAQ chatbot with mix of simple lookups and complex questions - -''' +Use case: FAQ chatbot with mix of simple lookups and complex questions -=== Pattern 9: time-based routing +=== Pattern 9: Time-based routing -*When to use*: Use cheaper models during off-peak hours +When to use: Use cheaper models during off-peak hours // PLACEHOLDER: Confirm if CEL has access to current timestamp -*Expression (if time functions available)*: +Expression (if time functions available): + [source,cel] ---- // PLACEHOLDER: Verify CEL time function availability @@ -549,23 +580,25 @@ now().hour >= 22 || now().hour < 6 // 10pm - 6am ---- -*What happens*: +What happens: + * Off-peak hours (10pm-6am) → Cheap model * Peak hours (6am-10pm) → Premium model -*Cost Impact*: +Cost impact: + * Optimize for user experience during peak usage * Save costs during low-traffic hours -*Use Case*: Consumer apps with time-zone-specific usage patterns +Use case: Consumer apps with time-zone-specific usage patterns -''' -=== Pattern 10: fallback chain (multi-level) +=== Pattern 10: Fallback chain (multi-level) -*When to use*: Complex fallback logic beyond simple primary/secondary +When to use: Complex fallback logic beyond simple primary/secondary + +Expression: -*Expression*: [source,cel] ---- request.headers["x-priority"] == "critical" @@ -576,26 +609,27 @@ request.headers["x-priority"] == "critical" ---- -*What happens*: +What happens: + * Critical requests → Always GPT-4o * Premium non-critical → Claude Sonnet * Everyone else → GPT-4o-mini -*Verify*: Test with different header combinations +Verify: Test with different header combinations -*Cost Impact*: Ensures SLA for critical requests while optimizing costs elsewhere +Cost impact: Ensures SLA for critical requests while optimizing costs elsewhere -*Use Case*: Production systems with SLA requirements +Use case: Production systems with SLA requirements -''' == Advanced CEL patterns -=== Pattern: default values with `has()` +=== Pattern: Default values with `has()` -*Problem*: Field might not exist in request +Problem: Field might not exist in request + +Expression: -*Expression*: [source,cel] ---- has(request.body.max_tokens) && request.body.max_tokens > 2000 @@ -604,11 +638,12 @@ has(request.body.max_tokens) && request.body.max_tokens > 2000 ---- -*What happens*: Safely checks if `max_tokens` exists before comparing +What happens: Safely checks if `max_tokens` exists before comparing + +=== Pattern: Multiple conditions with parentheses -=== Pattern: multiple conditions with parentheses +Expression: -*Expression*: [source,cel] ---- (request.headers["x-user-tier"] == "premium" || @@ -619,11 +654,12 @@ request.headers["x-environment"] == "production" ---- -*What happens*: Premium users OR VIP customer, AND production → GPT-4o +What happens: Premium users OR VIP customer, AND production → GPT-4o -=== Pattern: regex matching +=== Pattern: Regex matching + +Expression: -*Expression*: [source,cel] ---- request.body.messages[0].content.matches("(?i)(urgent|asap|emergency)") @@ -632,11 +668,12 @@ request.body.messages[0].content.matches("(?i)(urgent|asap|emergency)") ---- -*What happens*: Messages containing "urgent", "ASAP", or "emergency" (case-insensitive) → GPT-4o +What happens: Messages containing "urgent", "ASAP", or "emergency" (case-insensitive) → GPT-4o + +=== Pattern: String array contains -=== Pattern: string array contains +Expression: -*Expression*: [source,cel] ---- ["customer_1", "customer_2", "customer_3"].exists(c, c == request.headers["x-customer-id"]) @@ -645,11 +682,12 @@ request.body.messages[0].content.matches("(?i)(urgent|asap|emergency)") ---- -*What happens*: Only specific customers get premium model +What happens: Only specific customers get premium model -=== Pattern: reject invalid requests +=== Pattern: Reject invalid requests + +Expression: -*Expression*: [source,cel] ---- !has(request.body.messages) || request.body.messages.size() == 0 @@ -658,7 +696,7 @@ request.body.messages[0].content.matches("(?i)(urgent|asap|emergency)") ---- -*What happens*: Requests without messages are rejected (400 error) +What happens: Requests without messages are rejected (400 error) == Test CEL expressions @@ -666,13 +704,13 @@ request.body.messages[0].content.matches("(?i)(urgent|asap|emergency)") // PLACEHOLDER: Add screenshot if UI has CEL editor with test mode -1. Navigate to Gateway → Routing Rules +1. Navigate to Gateways → Routing Rules 2. Enter CEL expression 3. Click "Test" 4. Input test headers/body 5. View evaluated result -=== Option 2: send test requests +=== Option 2: Send test requests [source,python] ---- @@ -701,7 +739,7 @@ test_cel_routing( ---- -=== Option 3: cli test (if available) +=== Option 3: CLI test (if available) [source,bash] ---- @@ -720,16 +758,18 @@ rpk cloud ai-gateway test-cel \ === Error: "unknown field" -*Symptom*: +Symptom: + [source,text] ---- Error: Unknown field 'request.headers.x-user-tier' ---- -*Cause*: Wrong syntax (dot notation instead of bracket notation for headers) +Cause: Wrong syntax (dot notation instead of bracket notation for headers) + +Fix: -*Fix*: [source,cel] ---- // Wrong @@ -742,16 +782,18 @@ request.headers["x-user-tier"] === Error: "type mismatch" -*Symptom*: +Symptom: + [source,text] ---- Error: Type mismatch: expected bool, got string ---- -*Cause*: Forgot comparison operator +Cause: Forgot comparison operator + +Fix: -*Fix*: [source,cel] ---- // Wrong (returns string) @@ -764,16 +806,17 @@ request.headers["tier"] == "premium" === Error: "field does not exist" -*Symptom*: +Symptom: + [source,text] ---- Error: No such key: max_tokens ---- -*Cause*: Accessing field that doesn't exist in request +Cause: Accessing field that doesn't exist in request -*Fix*: +Fix: [source,cel] ---- // Wrong (crashes if max_tokens not in request) @@ -786,16 +829,18 @@ has(request.body.max_tokens) && request.body.max_tokens > 1000 === Error: "index out of bounds" -*Symptom*: +Symptom: + [source,text] ---- Error: Index 0 out of bounds for array of size 0 ---- -*Cause*: Accessing array element that doesn't exist +Cause: Accessing array element that doesn't exist + +Fix: -*Fix*: [source,cel] ---- // Wrong (crashes if messages empty) @@ -810,21 +855,23 @@ request.body.messages.size() > 0 && request.body.messages[0].content.contains("t === Expression complexity -*Fast* (<1ms evaluation): +Fast (<1ms evaluation): + [source,cel] ---- request.headers["tier"] == "premium" ? "openai/gpt-4o" : "openai/gpt-4o-mini" ---- -*Slower* (~5-10ms evaluation): +Slower (~5-10ms evaluation): + [source,cel] ---- request.body.messages[0].content.matches("complex.*regex.*pattern") ---- -*Recommendation*: Keep expressions simple. Complex regex can add latency. +Recommendation: Keep expressions simple. Complex regex can add latency. === Number of evaluations @@ -866,15 +913,12 @@ Each request evaluates CEL expression once. Total latency impact: == Next steps -* *Apply CEL Routing* → [Gateway Configuration Guide](// PLACEHOLDER: link) -* *Test Routing* → [End-to-End Validation](// PLACEHOLDER: link) -* *Monitor Routing Decisions* → [Observability: Logs](// PLACEHOLDER: link) -* *Optimize Costs* → [Cost Optimization Guide](// PLACEHOLDER: link) -* *Multi-Tenancy Patterns* → [Multi-Tenancy Guide](// PLACEHOLDER: link) +* *Apply CEL routing* → [Gateway Configuration Guide](// PLACEHOLDER: link) +* *Test routing* → [End-to-End Validation](// PLACEHOLDER: link) +* *Monitor routing decisions* → [Observability: Logs](// PLACEHOLDER: link) +* *Optimize costs* → [Cost Optimization Guide](// PLACEHOLDER: link) +* *Multi-tenancy patterns* → [Multi-Tenancy Guide](// PLACEHOLDER: link) == Related pages * [Quickstart](// PLACEHOLDER: link) -* [Provider Pools & Fallback](// PLACEHOLDER: link) -* [Rate Limiting](// PLACEHOLDER: link) -* [Observability](// PLACEHOLDER: link) diff --git a/modules/ai-agents/pages/ai-gateway/index.adoc b/modules/ai-agents/pages/ai-gateway/index.adoc new file mode 100644 index 000000000..a84ffbf2a --- /dev/null +++ b/modules/ai-agents/pages/ai-gateway/index.adoc @@ -0,0 +1,3 @@ += AI Gateway +:description: Learn how to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. +:page-layout: index diff --git a/modules/ai-agents/partials/mcp-aggregation-guide.adoc b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc similarity index 83% rename from modules/ai-agents/partials/mcp-aggregation-guide.adoc rename to modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc index c09684357..37c03603a 100644 --- a/modules/ai-agents/partials/mcp-aggregation-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc @@ -1,19 +1,22 @@ -= MCP aggregation & orchestration guide += DRAFT: MCP Aggregation and Orchestration Guide +:description: Quickstart to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. == Overview AI Gateway provides MCP (Model Context Protocol) aggregation, allowing AI agents to access tools from multiple MCP servers through a single unified endpoint. This eliminates the need for agents to manage multiple MCP connections and significantly reduces token costs through deferred tool loading. -*MCP Aggregation Benefits*: -* *Single Endpoint*: One MCP endpoint aggregates all approved MCP servers -* *Token Reduction*: 80-90% fewer tokens through deferred tool loading -* *Centralized Governance*: Admin-approved MCP servers only -* *Orchestration*: JavaScript-based orchestrator reduces multi-step round trips -* *Security*: Controlled tool execution environment +MCP aggregation benefits: -== What is mcp? +* Single endpoint: One MCP endpoint aggregates all approved MCP servers +* Token reduction: 80-90% fewer tokens through deferred tool loading +* Centralized governance: Admin-approved MCP servers only +* Orchestration: JavaScript-based orchestrator reduces multi-step round trips +* Security: Controlled tool execution environment + +== What is MCO? *Model Context Protocol (MCP)* is a standard for exposing tools (functions) that AI agents can discover and invoke. MCP servers provide tools like: + * Database queries * File system operations * API integrations (CRM, payment, analytics) @@ -22,12 +25,14 @@ AI Gateway provides MCP (Model Context Protocol) aggregation, allowing AI agents * Workflow automation *Without AI Gateway*: + * Agent connects to each MCP server individually * Agent loads ALL tools from ALL servers upfront (high token cost) * No centralized governance or security * Complex configuration *With AI Gateway*: + * Agent connects to gateway's unified `/mcp` endpoint * Gateway aggregates tools from approved MCP servers * Deferred loading: Only search + orchestrator tools sent initially @@ -81,9 +86,10 @@ AI Gateway provides MCP (Model Context Protocol) aggregation, allowing AI agents == MCP request lifecycle -=== 1. tool discovery (initial connection) +=== 1. Tool discovery (initial connection) + +Agent request: -*Agent Request*: [source,http] ---- GET /mcp/tools @@ -94,7 +100,8 @@ Headers: ---- -*Gateway Response* (with deferred loading): +Gateway response (with deferred loading): + [source,json] ---- { @@ -126,16 +133,18 @@ Headers: ---- -*Note*: Only 2 tools returned initially (search + orchestrator), not all 50+ tools from all MCP servers. +Note: Only 2 tools returned initially (search + orchestrator), not all 50+ tools from all MCP servers. + +Token savings: -*Token Savings*: * Without deferred loading: ~5,000-10,000 tokens (all tool definitions) * With deferred loading: ~500-1,000 tokens (2 tool definitions) -* *80-90% reduction* +* 80-90% reduction + +=== 2. Tool query (when agent needs specific tool) -=== 2. tool query (when agent needs specific tool) +Agent request: -*Agent Request*: [source,http] ---- POST /mcp/tools/search_tools @@ -149,7 +158,8 @@ Body: ---- -*Gateway Response*: +Gateway response: + [source,json] ---- { @@ -183,11 +193,12 @@ Body: ---- -*Agent receives only relevant tools* based on query. +Agent receives only relevant tools based on query. + +=== 3. Tool execution -=== 3. tool execution +Agent request: -*Agent Request*: [source,http] ---- POST /mcp/tools/execute_sql @@ -202,12 +213,14 @@ Body: ---- -*Gateway*: +Gateway: + 1. Routes to appropriate MCP server (database-server) 2. Executes tool 3. Returns result -*Gateway Response*: +Gateway response: + [source,json] ---- { @@ -220,19 +233,21 @@ Body: ---- -*Agent receives result* and can continue reasoning. +Agent receives result and can continue reasoning. -== Deferred tool loading: deep dive +== Deferred tool loading === How it works -*Traditional MCP (No Deferred Loading)*: +Traditional MCP (No deferred loading): + 1. Agent connects to MCP endpoint 2. Gateway sends ALL tools from ALL MCP servers (50+ tools) 3. Agent includes ALL tool definitions in EVERY LLM request 4. High token cost: ~5,000-10,000 tokens per request -*Deferred Loading (AI Gateway)*: +Deferred loading (AI Gateway): + 1. Agent connects to MCP endpoint with `rp-aigw-mcp-deferred: true` header 2. Gateway sends only 2 tools: `search_tools` + `orchestrator` 3. Agent includes only 2 tool definitions in LLM request (~500-1,000 tokens) @@ -241,17 +256,19 @@ Body: * Gateway returns matching tools * Agent calls specific tool (e.g., `execute_sql`) 5. Total token cost: Initial 500-1,000 + per-query ~200-500 - * *Still 80-90% lower than loading all tools* + * Still 80-90% lower than loading all tools === When to use deferred loading -*Use Deferred Loading When*: +Use deferred loading when: + * You have 10+ tools across multiple MCP servers * Agents don't need all tools for every request * Token costs are a concern * Agents can handle multi-step workflows (search → execute) -*Don't Use Deferred Loading When*: +Don't use deferred loading when: + * You have <5 tools total (overhead not worth it) * Agents need all tools for every request (rare) * Latency is more important than token costs (deferred adds 1 round trip) @@ -260,7 +277,8 @@ Body: // PLACEHOLDER: Add UI path or configuration method -*Option 1: Enable at Gateway Level* (recommended) +Option 1: Enable at gateway level (recommended) + [source,yaml] ---- # PLACEHOLDER: Actual configuration format @@ -269,7 +287,8 @@ mcp: ---- -*Option 2: Enable Per-Request* (agent-controlled) +Option 2: Enable per-request (agent-controlled) + [source,python] ---- # Agent includes header @@ -282,24 +301,27 @@ headers = { === Measure token savings -*Compare token usage before/after deferred loading*: +Compare token usage before/after deferred loading: + +1. Check logs without deferred loading: -1. *Check Logs Without Deferred Loading*: * Filter: Gateway = your-gateway, Model = your-model, Date = before enabling * Average tokens per request: // PLACEHOLDER: measure -2. *Enable Deferred Loading* +2. Enable deferred loading + +3. Check logs after deferred loading: -3. *Check Logs After Deferred Loading*: * Filter: Same gateway/model, Date = after enabling * Average tokens per request: // PLACEHOLDER: measure -4. *Calculate Savings*: +4. Calculate savings: + ``` Savings % = ((Before - After) / Before) × 100 ``` -*Expected Results*: 80-90% reduction in average tokens per request +Expected Results: 80-90% reduction in average tokens per request == Orchestrator: multi-step workflows @@ -307,19 +329,22 @@ headers = { The *orchestrator* is a special tool that executes JavaScript workflows, reducing multi-step interactions from multiple round trips to a single request. -*Without Orchestrator*: +Without Orchestrator: + 1. Agent: "Search vector database for relevant docs" → Round trip 1 2. Agent receives results, evaluates: "Results insufficient" 3. Agent: "Fallback to web search" → Round trip 2 4. Agent receives results, processes → Round trip 3 5. *Total: 3 round trips* (high latency, 3× token cost) -*With Orchestrator*: +With Orchestrator: + 1. Agent: "Execute workflow: Search vector DB → if insufficient, fallback to web search" 2. Gateway executes entire workflow in JavaScript 3. Agent receives final result → *1 round trip* -*Benefits*: +Benefits: + * *Latency Reduction*: 1 round trip vs 3+ * *Token Reduction*: No intermediate LLM calls needed * *Reliability*: Workflow logic executes deterministically @@ -327,22 +352,25 @@ The *orchestrator* is a special tool that executes JavaScript workflows, reducin === When to use orchestrator -*Use Orchestrator When*: +Use orchestrator when: + * Multi-step workflows with conditional logic (if/else) * Fallback patterns (try A, if fails, try B) * Sequential tool calls with dependencies * Loop-based operations (iterate, aggregate) -*Don't Use Orchestrator When*: +Don't use orchestrator when: + * Single tool call (no benefit) * Agent needs to reason between steps (orchestrator is deterministic) * Workflow requires LLM judgment at each step === Orchestrator example: search with fallback -*Scenario*: Search vector database; if results insufficient, fallback to web search. +Scenario: Search vector database; if results insufficient, fallback to web search. + +Without Orchestrator (3 round trips): -*Without Orchestrator* (3 round trips): [source,python] ---- # Agent's internal reasoning (3 separate LLM calls) @@ -362,7 +390,8 @@ else: ---- -*With Orchestrator* (1 round trip): +With Orchestrator (1 round trip): + [source,python] ---- # Agent invokes orchestrator once @@ -392,18 +421,20 @@ results = call_tool("orchestrator", { ---- -*Savings*: -* *Latency*: ~3-5 seconds (3 round trips) → ~1-2 seconds (1 round trip) -* *Tokens*: ~1,500 tokens (3 LLM calls) → ~500 tokens (1 LLM call) -* *Cost*: ~$0.0075 → ~$0.0025 (67% reduction) +Savings: + +* Latency: ~3-5 seconds (3 round trips) → ~1-2 seconds (1 round trip) +* Tokens: ~1,500 tokens (3 LLM calls) → ~500 tokens (1 LLM call) +* Cost: ~$0.0075 → ~$0.0025 (67% reduction) === Orchestrator API // PLACEHOLDER: Confirm orchestrator API details -*Tool Name*: `orchestrator` +Tool name: `orchestrator` + +Input schema: -*Input Schema*: [source,json] ---- { @@ -413,24 +444,27 @@ results = call_tool("orchestrator", { ---- -*Available in Workflow*: +Available in workflow: + * `tools.{tool_name}(params)`: Call any tool from approved MCP servers * `context.{variable}`: Access context variables * Standard JavaScript: `if`, `for`, `while`, `try/catch`, `async/await` -*Security*: +Security: + * Sandboxed execution (no file system, network, or system access) * Timeout: // PLACEHOLDER: e.g., 30 seconds * Memory limit: // PLACEHOLDER: e.g., 128MB -*Limitations*: +Limitations: + * Cannot call external APIs directly (must use MCP tools) * Cannot import npm packages (built-in JS only) * // PLACEHOLDER: Other limitations? === Orchestrator example: data aggregation -*Scenario*: Fetch user data from database, calculate summary statistics. +Scenario: Fetch user data from database, calculate summary statistics. [source,python] ---- @@ -470,7 +504,8 @@ results = call_tool("orchestrator", { ---- -*Output*: +Output: + [source,json] ---- { @@ -485,20 +520,23 @@ results = call_tool("orchestrator", { ---- -*vs Without Orchestrator*: +vs Without Orchestrator: + * Would require fetching all users to agent → agent processes → 2 round trips * Orchestrator: All processing in gateway → 1 round trip === Orchestrator best practices -*DO*: +DO: + * Use for deterministic workflows (same input → same output) * Use for sequential operations with dependencies * Use for fallback patterns * Handle errors with `try/catch` * Keep workflows readable (add comments) -*DON'T*: +DON'T: + * Use for workflows requiring LLM reasoning at each step (let agent handle that) * Execute long-running operations (timeout will hit) * Access external resources (use MCP tools instead) @@ -510,16 +548,20 @@ results = call_tool("orchestrator", { // PLACEHOLDER: Add UI path for MCP server management -*Prerequisites*: +Prerequisites: + * MCP server URL * Authentication method (if required) * List of tools to enable -*Steps*: -1. *Navigate to MCP Servers*: +Steps: + +1. Navigate to MCP servers: + * Console → AI Gateway → MCP Servers → Add Server -2. *Configure Server*: +2. Configure server: + ```yaml # PLACEHOLDER: Actual configuration format name: database-server @@ -533,47 +575,54 @@ results = call_tool("orchestrator", { * describe_table ``` -3. *Test Connection*: +3. Test connection: + * Gateway attempts connection to MCP server * Verifies authentication * Retrieves tool list -4. *Enable Server*: +4. Enable server: + * Server status: Active * Tools available to agents -*Common MCP Servers*: -* *Database*: PostgreSQL, MySQL, MongoDB query tools -* *Filesystem*: Read/write/search files -* *API Integrations*: Slack, GitHub, Salesforce, Stripe -* *Search*: Web search, vector search, enterprise search -* *Code Execution*: Python, JavaScript sandboxes -* *Workflow*: Zapier, n8n integrations +Common MCP servers: + +* Database: PostgreSQL, MySQL, MongoDB query tools +* Filesystem: Read/write/search files +* API Integrations: Slack, GitHub, Salesforce, Stripe +* Search: Web search, vector search, enterprise search +* Code Execution: Python, JavaScript sandboxes +* Workflow: Zapier, n8n integrations === MCP server approval workflow -*Why Approval is Required*: +Why approval is required: + * Security: Prevent agents from accessing unauthorized systems * Governance: Control which tools are available * Cost: Some tools are expensive (API calls, compute) * Compliance: Audit trail of approved tools -*Approval Process*: +Approval process: + // PLACEHOLDER: Confirm if there's an approval workflow or if admins directly enable servers -1. *Request*: User/team requests MCP server -2. *Review*: Admin reviews security, cost, necessity -3. *Approval/Rejection*: Admin decision -4. *Configuration*: If approved, admin adds server to gateway +1. Request: User/team requests MCP server +2. Review: Admin reviews security, cost, necessity +3. Approval/Rejection: Admin decision +4. Configuration: If approved, admin adds server to gateway + +Rejected server behavior: -*Rejected Server Behavior*: * Server not listed in tool discovery * Agent cannot query or invoke tools from this server * Requests return `403 Forbidden` === Restrict MCP server access -*Per-Gateway Restrictions*: +Per-gateway restrictions: + [source,yaml] ---- # PLACEHOLDER: Actual configuration format @@ -592,7 +641,8 @@ gateways: ---- -*Use Cases*: +Use cases: + * Production gateway: Only production-safe tools * Staging gateway: All tools for testing * Customer-specific gateway: Only tools relevant to customer @@ -601,22 +651,25 @@ gateways: // PLACEHOLDER: How is MCP server versioning handled? -*Challenge*: MCP server updates may change tool schemas +Challenge: MCP server updates may change tool schemas -*Recommendations*: -1. *Pin Versions* (if supported): +Recommendations: + +1. Pin versions (if supported): ```yaml mcp_servers: * name: database-server version: "1.2.3" # Pin to specific version ``` -2. *Test in Staging First*: +2. Test in staging first: + * Update MCP server in staging gateway * Test agent workflows * Promote to production when validated -3. *Monitor Breaking Changes*: +3. Monitor breaking changes: + * Subscribe to MCP server changelogs * Set up alerts for schema changes @@ -625,6 +678,7 @@ gateways: === Logs MCP tool invocations appear in request logs with: + * Tool name * MCP server * Input parameters @@ -632,14 +686,16 @@ MCP tool invocations appear in request logs with: * Execution time * Errors (if any) -*Filter Logs by MCP*: +Filter logs by MCP: + [source,text] ---- Filter: request.path.startsWith("/mcp") ---- -*Common Log Fields*: +Common log fields: + | Field | Description | Example | |-------|-------------|---------| | Tool | Tool invoked | `execute_sql` | @@ -653,7 +709,8 @@ Filter: request.path.startsWith("/mcp") // PLACEHOLDER: Confirm if MCP-specific metrics exist -*MCP-Specific Metrics* (if available): +MCP-specific metrics (if available): + * MCP requests per second * Tool invocation count (by tool, by MCP server) * MCP latency (p50, p95, p99) @@ -661,7 +718,8 @@ Filter: request.path.startsWith("/mcp") * Orchestrator execution count * Orchestrator execution time -*Dashboard*: MCP Analytics +Dashboard: MCP Analytics + * Top tools by usage * Top MCP servers by latency * Error rate by MCP server @@ -669,39 +727,45 @@ Filter: request.path.startsWith("/mcp") === Debug MCP issues -*Issue: "Tool not found"* +Issue: "Tool not found" + +Possible causes: -*Possible Causes*: 1. MCP server not added to gateway 2. Tool not enabled in MCP server configuration 3. Deferred loading enabled but agent didn't query for tool first -*Solution*: +Solution: + 1. Verify MCP server is active: // PLACEHOLDER: UI path 2. Verify tool is in enabled_tools list 3. If deferred loading: Agent must call `search_tools` first -*Issue: "MCP server timeout"* +Issue: "MCP server timeout" + +Possible causes: -*Possible Causes*: 1. MCP server is down/unreachable 2. Tool execution is slow (e.g., expensive database query) 3. Gateway timeout too short -*Solution*: +Solution: + 1. Check MCP server health 2. Optimize tool (e.g., add database index) 3. Increase timeout: // PLACEHOLDER: How to configure? -*Issue: "Orchestrator workflow failed"* +Issue: "Orchestrator workflow failed" + +Possible causes: -*Possible Causes*: 1. JavaScript syntax error 2. Tool invocation failed inside workflow 3. Timeout exceeded 4. Memory limit exceeded -*Solution*: +Solution: + 1. Test workflow syntax in JavaScript playground 2. Check logs for tool error inside orchestrator 3. Simplify workflow or increase timeout @@ -713,32 +777,37 @@ Filter: request.path.startsWith("/mcp") // PLACEHOLDER: Confirm sandboxing implementation -*Orchestrator Sandbox*: +Orchestrator Sandbox: + * No file system access * No network access (except via MCP tools) * No system calls * Memory limit: // PLACEHOLDER: e.g., 128MB * Execution timeout: // PLACEHOLDER: e.g., 30s -*MCP Tool Execution*: +MCP tool execution: + * Tools execute in MCP server's environment (not gateway) * Gateway does not execute tool code (only proxies requests) * Security is MCP server's responsibility === Authentication -*Gateway → MCP Server*: +Gateway → MCP server: + * Bearer token (most common) * API key * mTLS (for high-security environments) -*Agent → Gateway*: +Agent → Gateway: + * Standard gateway authentication (Redpanda Cloud token) * `rp-aigw-id` header identifies gateway (and its approved MCP servers) === Audit trail All MCP operations logged: + * Who (agent/user) invoked tool * When (timestamp) * What tool was invoked @@ -746,19 +815,21 @@ All MCP operations logged: * What result was returned * Whether it succeeded or failed -*Use Case*: Compliance, security investigation, debugging +Use case: Compliance, security investigation, debugging === Restrict dangerous tools -*Recommendation*: Don't enable destructive tools in production gateways +Recommendation: Don't enable destructive tools in production gateways + +Examples of dangerous tools*: -*Examples of Dangerous Tools*: * File deletion (`delete_file`) * Database writes without safeguards (`execute_sql` with UPDATE/DELETE) * Payment operations (`charge_customer`) * System commands (`execute_bash`) -*Best Practice*: +Best practice: + * Read-only tools in production gateway * Write tools only in staging gateway (with approval workflows) * Wrap dangerous operations in MCP server with safeguards (e.g., "require confirmation token") @@ -767,9 +838,10 @@ All MCP operations logged: === Combine MCP with CEL routing -*Use Case*: Route agents to different MCP servers based on customer tier +Use case: Route agents to different MCP servers based on customer tier + +CEL expression: -*CEL Expression*: [source,cel] ---- request.headers["x-customer-tier"] == "enterprise" @@ -778,19 +850,21 @@ request.headers["x-customer-tier"] == "enterprise" ---- -*Result*: +Result: + * Enterprise customers: Access to proprietary data, expensive APIs * Basic customers: Access to public data, free APIs === MCP with provider pools -*Scenario*: Different agents use different models + different tools +Scenario: Different agents use different models + different tools + +Configuration: -*Configuration*: * Gateway A: GPT-4o + database + CRM MCP servers * Gateway B: Claude Sonnet + web search + analytics MCP servers -*Use Case*: Optimize model-tool pairing (some models better at certain tools) +Use case: Optimize model-tool pairing (some models better at certain tools) == Integration examples @@ -860,7 +934,7 @@ if response.choices[0].message.tool_calls: ---- -=== Claude code cli +=== Claude Code CLI [source,bash] ---- @@ -909,15 +983,14 @@ response = agent.run("Find all premium users in the database") == Next steps -* *Configure MCP Servers* → [MCP Server Administration Guide](// PLACEHOLDER: link) -* *Write Orchestrator Workflows* → [Orchestrator Examples](// PLACEHOLDER: link) -* *Monitor MCP Usage* → [Observability: MCP Metrics](// PLACEHOLDER: link) -* *Optimize Token Costs* → [Cost Optimization Guide](// PLACEHOLDER: link) -* *Build Agentic Workflows* → [Agent Patterns Guide](// PLACEHOLDER: link) +* *Configure MCP servers* → [MCP Server Administration Guide](// PLACEHOLDER: link) +* *Write Orchestrator workflows* → [Orchestrator Examples](// PLACEHOLDER: link) +* *Monitor MCP usage* → [Observability: MCP Metrics](// PLACEHOLDER: link) +* *Optimize token costs* → [Cost Optimization Guide](// PLACEHOLDER: link) +* *Build agentic workflows* → [Agent Patterns Guide](// PLACEHOLDER: link) == Related pages * [Quickstart](// PLACEHOLDER: link) * [CEL Routing](// PLACEHOLDER: link) * [Observability: Logs](// PLACEHOLDER: link) -* [Security & Data Handling](// PLACEHOLDER: link) diff --git a/modules/ai-agents/partials/migration-guide.adoc b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc similarity index 83% rename from modules/ai-agents/partials/migration-guide.adoc rename to modules/ai-agents/pages/ai-gateway/migration-guide.adoc index ffdd54a17..0b8260b4c 100644 --- a/modules/ai-agents/partials/migration-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc @@ -1,23 +1,28 @@ -= Migration guide: from direct provider integration to AI gateway += DRAFT: Migrate from Direct Provider Integration to AI Gateway +:description: Quickstart to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. == Overview This guide helps you migrate existing applications from direct LLM provider integrations (OpenAI, Anthropic, etc.) to Redpanda AI Gateway. The migration is designed to be *incremental and reversible*, allowing you to test thoroughly before fully committing. -*Migration Time*: 10-30 minutes for most applications -*Downtime Required*: None (supports parallel operation) -*Rollback Difficulty*: Easy (feature flag or environment variable) +Migration time: 10-30 minutes for most applications + +Downtime required: None (supports parallel operation) + +Rollback difficulty: Easy (feature flag or environment variable) == Prerequisites Before migrating, ensure you have: -* ✅ AI Gateway configured in your Redpanda Cloud account -* ✅ Providers and models enabled (see [Admin Guide: Providers](// PLACEHOLDER: link)) -* ✅ Gateway created with appropriate policies (see [Gateway Creation Guide](// PLACEHOLDER: link)) -* ✅ Your gateway ID (`rp-aigw-id` header value) -* ✅ Your gateway endpoint URL - +//// == Migration strategy @@ -354,8 +355,6 @@ Common issues: * `Model not found` → Ensure model is enabled in gateway configuration * No `rp-aigw-id` header → Verify header is set in `default_headers` -See [Troubleshooting Guide](// PLACEHOLDER: link) for more details. - === Step 4: Verify in observability dashboard After successful test: @@ -370,7 +369,7 @@ After successful test: * Token count: ~10 prompt + ~10 completion * Cost: // PLACEHOLDER: expected cost -*If request doesn't appear*: Check [End-to-End Validation Guide](// PLACEHOLDER: link) +*If request doesn't appear*: Verify gateway ID and authentication token are correct. === Step 5: Enable gateway for subset of traffic @@ -780,7 +779,7 @@ If latency is significantly higher: 3. Review CEL routing complexity 4. Check for rate limiting (adds retry latency) -Solution: See [Performance Optimization Guide](// PLACEHOLDER: link) +Solution: Review geographic routing and provider pool configuration. === Issue: Requests not appearing in dashboard @@ -790,7 +789,7 @@ Causes: 2. Request failed before reaching gateway 3. UI delay (logs may take // PLACEHOLDER: Xs to appear) -Solution: See [End-to-End Validation Guide](// PLACEHOLDER: link) +Solution: Verify gateway ID and check for UI delay (logs may take a few seconds to appear). === Issue: Different response format @@ -931,12 +930,5 @@ model = "anthropic/claude-sonnet-3.5" # Was openai/gpt-4o == Next steps -* Configure routing policies → [CEL Routing Guide](// PLACEHOLDER: link) -* Explore MCP → [MCP Aggregation Guide](// PLACEHOLDER: link) - -== Related pages - -* [Quickstart](// PLACEHOLDER: link) -* [OpenAI Integration](// PLACEHOLDER: link) -* [Anthropic Integration](// PLACEHOLDER: link) -* [LangChain Integration](// PLACEHOLDER: link) +* xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Configure advanced routing policies. +* xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Explore MCP aggregation. diff --git a/modules/ai-agents/pages/ai-gateway/observability-logs.adoc b/modules/ai-agents/pages/ai-gateway/observability-logs.adoc index 0203cb863..ff82edbb8 100644 --- a/modules/ai-agents/pages/ai-gateway/observability-logs.adoc +++ b/modules/ai-agents/pages/ai-gateway/observability-logs.adoc @@ -24,7 +24,7 @@ Use logs for: * Understanding which provider handled a request * Investigating latency spikes or errors for specific users -Use metrics for: Aggregate analytics, trends, cost tracking across time → See [Observability: Metrics](// PLACEHOLDER: link) +Use metrics for: Aggregate analytics, trends, cost tracking across time. See xref:ai-agents:ai-gateway/observability-metrics.adoc[]. == Where to find logs @@ -628,7 +628,7 @@ Retention period: // PLACEHOLDER: e.g., 30 days, 90 days, configurable After retention period: * Logs are deleted automatically -* Aggregate metrics retained longer (see [Metrics](// PLACEHOLDER: link)) +* Aggregate metrics retained longer (see xref:ai-agents:ai-gateway/observability-metrics.adoc[]) Export logs (if needed for longer retention): @@ -671,7 +671,6 @@ Supported integrations (if any): * CloudWatch Logs → For AWS deployments * // PLACEHOLDER: Others? -See [Observability Integrations](// PLACEHOLDER: link) for setup guides. == Privacy and security @@ -772,4 +771,4 @@ Note: Cost estimates are approximate. Use provider invoices for billing. == Next steps -* Aggregate analytics → [Observability: Metrics](// PLACEHOLDER: link) \ No newline at end of file +* xref:ai-agents:ai-gateway/observability-metrics.adoc[]: Aggregate analytics and cost tracking. \ No newline at end of file diff --git a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc b/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc index bd8ea95e3..0dc06bc3e 100644 --- a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc +++ b/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc @@ -24,7 +24,7 @@ Use metrics for: * Capacity planning * Model/provider comparison -Use logs for: Debugging specific requests, viewing full prompts/responses. See [Observability: Logs](// PLACEHOLDER: link) +Use logs for: Debugging specific requests, viewing full prompts/responses. See xref:ai-agents:ai-gateway/observability-logs.adoc[]. == Where to find metrics @@ -499,9 +499,6 @@ alerts: channels: [pagerduty] ---- - -See [Alerting Guide](// PLACEHOLDER: link) for detailed setup. - == Export metrics // PLACEHOLDER: Confirm export capabilities @@ -559,8 +556,6 @@ Supported integrations (if any): * Grafana: Pre-built dashboards * // PLACEHOLDER: Others? -See [Observability Integrations](// PLACEHOLDER: link) for setup guides. - == Common analysis tasks === Task 1: "Are we staying within budget?" @@ -864,9 +859,9 @@ Possible causes: Solution: 1. Remove filters, widen time range -2. Send test request (see [Quickstart](// PLACEHOLDER: link)) +2. Send test request (see xref:ai-agents:ai-gateway/quickstart-enhanced.adoc[]) 3. Check permissions with admin == Next steps -* View individual requests → [Observability: Logs](// PLACEHOLDER: link) +* xref:ai-agents:ai-gateway/observability-logs.adoc[]: View individual requests and debug issues. From 8b7bcf5b2c61793a626d90636552e11f6b36ad53 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Wed, 21 Jan 2026 09:34:07 +0000 Subject: [PATCH 19/97] Improve customer support tutorial with explicit instructions - Change negative headings to positive action-oriented headings - Add explicit 'Enter this query' instructions for each test scenario - Add guidance on what to watch for in the conversation panel - Specify when to start new sessions for context clearing --- .../tutorials/customer-support-agent.adoc | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index bcc324632..2968b9110 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -121,9 +121,9 @@ Wait for the server status to show *Running*. You now have three focused tools t The system prompt teaches the agent how to orchestrate tools. Without explicit guidance, the agent must guess when to use each tool, often choosing incorrectly or ignoring tools entirely. -=== Implicit tool selection +=== Specify when to use each tool -Make tool usage explicit. +The prompt must explicitly state when to invoke each tool. When the prompt doesn't specify when to use tools, the agent must guess based on tool names and descriptions alone. This leads to wrong tool choices, unnecessary calls, or skipped tools entirely. @@ -146,7 +146,7 @@ When to use tools: Explicit criteria create reliable tool selection. The agent follows clear rules instead of guessing. -=== Implicit tool chaining +=== Define tool chaining logic Specify how tool results inform the next action. @@ -169,7 +169,7 @@ If order is "shipped", follow up with get_shipping_info to provide tracking deta The agent uses the first tool's result (whether the status is "shipped") to decide whether to invoke the second tool. This creates context-aware behavior. -=== Missing error constraints +=== Set error handling constraints Prevent fabrication when tools fail. @@ -273,47 +273,71 @@ Testing reveals how the agent makes decisions. Watch the conversation panel in t === Tool chaining based on status -.Query +Test how the agent chains tools based on order status. + +Enter this query in the Inspector: + ---- Hi, I'd like to check on order ORD-12345 ---- -The agent uses the first tool's result (whether the status is "shipped") to decide whether to invoke the second tool. This demonstrates context-aware tool chaining. +Watch the conversation panel. The agent calls `get_order_status` first, sees the status is "shipped", then automatically follows up with `get_shipping_info` to provide tracking details. The agent uses the first tool's result to decide whether to invoke the second tool. + +Now try this query with a different order: + +---- +Check order ORD-67890 +---- -Try changing ORD-12345 to ORD-67890. This order has status "processing", so the agent calls only `get_order_status` (no shipping info exists yet). The agent chains tools only when appropriate. +This order has status "processing", so the agent calls only `get_order_status`. Since the order hasn't shipped yet, the agent skips `get_shipping_info`. The agent chains tools only when appropriate. === Clarification before tool invocation -Clear the conversation history to reset context. Then test this query: +Test how the agent handles incomplete information. + +Click *Clear context* to clear the conversation history. Then enter this query: -.Query ---- Where is my order? ---- -The agent recognizes incomplete requests and asks clarifying questions instead of guessing or calling tools with missing parameters. This demonstrates pre-condition checking. +The agent recognizes the request is missing an order ID and asks the customer to provide it. Watch the conversation panel—the agent calls zero tools. Instead of guessing or fabricating information, it asks a clarifying question. -Notice the agent calls zero tools. Effective orchestration includes knowing when NOT to invoke tools. +This demonstrates pre-condition checking. Effective orchestration includes knowing when NOT to invoke tools. === List handling -.Query +Test how the agent formats multiple results. + +Enter this query: + ---- Can you show me my recent orders? My customer ID is CUST-100. ---- -The agent handles list results by formatting them clearly for users. Try CUST-999 (returns empty list) to see how the agent handles the no-results case. +The agent calls `get_customer_history` and receives multiple orders. Watch how it formats the list clearly for the customer, showing details for each order. + +Now test the empty results case with this query: + +---- +Show my order history for customer ID CUST-999 +---- + +The agent receives an empty list and explains that no orders were found, asking the customer to verify their ID. === Error recovery -.Query +Test how the agent handles missing data. + +Enter this query: + ---- Check order ORD-99999 ---- -The agent detects tool failures and handles them gracefully. Critically, the agent does NOT fabricate tracking numbers or order details. This demonstrates error recovery without hallucination. +The tool returns no data for this order ID. Watch how the agent responds—it explains the order wasn't found and asks the customer to verify the order ID. Critically, the agent does NOT fabricate tracking numbers or order details. -Compare this to what happens without the "Never make up tracking numbers" constraint: remove that line from the system prompt and retest. The agent might invent plausible-sounding but fake tracking information. +This demonstrates error recovery without hallucination. The "Never make up tracking numbers" constraint in the system prompt prevents the agent from inventing plausible-sounding but fake information. == Troubleshoot From a1b32072944e3b558544cd4543bb250bf473742e Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Wed, 21 Jan 2026 09:34:45 +0000 Subject: [PATCH 20/97] Lowercase inspector when not writing about the UI button --- modules/ai-agents/pages/agents/troubleshooting.adoc | 6 +++--- modules/ai-agents/pages/mcp/remote/create-tool.adoc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ai-agents/pages/agents/troubleshooting.adoc b/modules/ai-agents/pages/agents/troubleshooting.adoc index cc8a23ae6..f8191b167 100644 --- a/modules/ai-agents/pages/agents/troubleshooting.adoc +++ b/modules/ai-agents/pages/agents/troubleshooting.adoc @@ -197,12 +197,12 @@ Diagnose and fix issues related to agent speed and resource consumption. . Use a faster model for simple queries: .. Haiku or GPT-4o Mini for straightforward tasks .. Reserve larger models for complex reasoning -. Review conversation history in Inspector to identify unnecessary tool calls. +. Review conversation history in the *Inspector* tab to identify unnecessary tool calls. . Optimize tool implementations: .. Add caching where appropriate .. Reduce query complexity .. Return only needed data (use pagination, filters) -. Clear conversation history in Inspector if context is very large. +. Clear the conversation history if the context is very large. **Prevention:** @@ -246,7 +246,7 @@ Efficiency guidelines: ---- . Switch to a more cost-effective model for simple queries. -. Clear conversation history periodically in Inspector. +. Clear conversation history periodically in the *Inspector* tab. **Prevention:** diff --git a/modules/ai-agents/pages/mcp/remote/create-tool.adoc b/modules/ai-agents/pages/mcp/remote/create-tool.adoc index 579728b2d..417953a23 100644 --- a/modules/ai-agents/pages/mcp/remote/create-tool.adoc +++ b/modules/ai-agents/pages/mcp/remote/create-tool.adoc @@ -6,7 +6,7 @@ // Learning objectives - what readers can do after reading this page: :learning-objective-1: Create a tool with the correct structure and MCP metadata :learning-objective-2: Map MCP parameters to component configuration fields using Bloblang -:learning-objective-3: Test tools using the MCP Inspector +:learning-objective-3: Test tools using the MCP inspector After xref:ai-agents:mcp/remote/quickstart.adoc[deploying your first MCP server], create custom tools that AI clients can discover and invoke. This guide walks you through the process using any Redpanda Connect component. From bbdabd88f119fa3ae01845c48a7336e99fcca26f Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Wed, 21 Jan 2026 15:58:38 +0000 Subject: [PATCH 21/97] Restructure observability docs for better information architecture Moved monitoring how-tos into context where users need them: - Monitor Agents now in agents section - Monitor MCP Servers now in mcp/remote section - Observability concepts remain centralized as single source of truth This follows the "procedures in context, concepts centralized" pattern, reducing navigation overhead and improving task completion. Also removed unnecessary observability index page since only one page remains in that section. Co-Authored-By: Claude Sonnet 4.5 --- docs-data/personas.yaml | 269 +++++++++++++++--- modules/ROOT/nav.adoc | 7 +- .../ai-agents/pages/agents/a2a-concepts.adoc | 2 +- .../pages/agents/architecture-patterns.adoc | 2 +- modules/ai-agents/pages/agents/concepts.adoc | 4 +- .../ai-agents/pages/agents/create-agent.adoc | 2 +- .../pages/agents/integration-overview.adoc | 2 +- .../monitor-agents.adoc | 2 +- modules/ai-agents/pages/agents/overview.adoc | 2 +- .../agents/pipeline-integration-patterns.adoc | 2 +- .../pages/agents/prompt-best-practices.adoc | 2 +- .../ai-agents/pages/agents/quickstart.adoc | 2 +- .../pages/agents/troubleshooting.adoc | 4 +- .../tutorials/customer-support-agent.adoc | 2 +- .../pages/mcp/local/configuration.adoc | 2 +- .../ai-agents/pages/mcp/local/overview.adoc | 2 +- .../ai-agents/pages/mcp/local/quickstart.adoc | 2 +- modules/ai-agents/pages/mcp/overview.adoc | 2 +- .../pages/mcp/remote/best-practices.adoc | 2 +- .../ai-agents/pages/mcp/remote/concepts.adoc | 4 +- .../pages/mcp/remote/create-tool.adoc | 2 +- .../pages/mcp/remote/manage-servers.adoc | 4 +- .../remote}/monitor-mcp-servers.adoc | 2 +- .../ai-agents/pages/mcp/remote/overview.adoc | 2 +- .../pages/mcp/remote/quickstart.adoc | 2 +- .../pages/mcp/remote/tool-patterns.adoc | 2 +- .../pages/mcp/remote/troubleshooting.adoc | 2 +- .../pages/observability/concepts.adoc | 6 +- .../ai-agents/pages/observability/index.adoc | 5 - 29 files changed, 265 insertions(+), 80 deletions(-) rename modules/ai-agents/pages/{observability => agents}/monitor-agents.adoc (99%) rename modules/ai-agents/pages/{observability => mcp/remote}/monitor-mcp-servers.adoc (98%) delete mode 100644 modules/ai-agents/pages/observability/index.adoc diff --git a/docs-data/personas.yaml b/docs-data/personas.yaml index 46d4e7912..f3368e85d 100644 --- a/docs-data/personas.yaml +++ b/docs-data/personas.yaml @@ -2,34 +2,77 @@ # # These personas represent the target audience for Redpanda Cloud documentation. # Use these when assigning :personas: attributes to documentation pages. +# +# This persona set covers two domains: +# 1. Streaming/Data Platform: Real-time data streaming, connectors, pipelines +# 2. Agentic Data Platform (ADP): AI agent development, governance, enterprise AI adoption schema_version: "1.0" repository: cloud-docs personas: - - id: app_developer - name: Application Developer - description: Builds applications that produce and consume data from Redpanda Cloud - experience_level: intermediate + # ============================================================================ + # TIER 1: Executive & Governance + # ============================================================================ + + - id: executive + name: Executive Stakeholder + description: CIO/CAIO/Head of AI Strategy driving enterprise AI adoption and governance + experience_level: executive goals: - - Connect applications to Redpanda Cloud clusters - - Produce and consume messages reliably - - Implement proper error handling and retries - - Optimize client performance + - Drive enterprise-wide AI adoption strategy + - Ensure ROI on AI investments + - Establish governance framework for agent deployments + - Manage cost and resource allocation + - Ensure compliance with organizational policies pain_points: - - Authentication and connection configuration - - Understanding Kafka client options - - Debugging connectivity issues - - Choosing the right client library + - Lack of visibility into agent usage and costs + - Difficulty enforcing governance at scale + - Unclear ROI metrics for AI initiatives + - Risk of shadow AI deployments + - Integration with existing enterprise systems content_preferences: - - Working code examples in multiple languages - - Connection configuration templates - - Client library comparisons - - Performance tuning guides + - High-level governance frameworks + - ROI and cost analysis + - Compliance and audit capabilities + - Executive dashboards and reporting + - Strategic planning guides typical_content_types: - - how-to - - tutorial + - overview + - concepts + - best-practices + + - id: security_leader + name: Security & Risk Leader + description: CISO/Compliance Officer protecting systems and enforcing data protection policies + experience_level: advanced + goals: + - Enforce agent policy and access controls + - Maintain audit trails for compliance + - Protect sensitive data and credentials + - Manage risk across agent deployments + - Ensure regulatory compliance + pain_points: + - Agent access to sensitive systems + - Lack of visibility into agent actions + - Difficult to audit agent behavior + - Credential management and rotation + - Compliance with data protection regulations + content_preferences: + - Security architecture patterns + - Policy enforcement mechanisms + - Audit trail documentation + - Compliance certification guides + - Incident response procedures + typical_content_types: + - concepts - reference + - best-practices + - troubleshooting + + # ============================================================================ + # TIER 2: Platform Operations + # ============================================================================ - id: platform_admin name: Platform Administrator @@ -55,47 +98,97 @@ personas: - reference - best-practices - - id: data_engineer - name: Data Engineer - description: Builds data pipelines using managed connectors and Redpanda Connect - experience_level: intermediate + - id: ai_platform_engineer + name: AI/ML Platform Engineer + description: Operates agent infrastructure, runtimes, and connectivity with governance controls + experience_level: advanced goals: - - Set up managed connectors to move data between systems - - Transform and route data reliably - - Monitor connector and pipeline health - - Handle errors and retries + - Deploy and operate agent runtime infrastructure + - Configure governance controls and policies + - Monitor agent performance and resource usage + - Onboard and manage MCP servers + - Ensure agent observability and debugging pain_points: - - Connector configuration complexity - - Debugging failed connectors - - Schema management and evolution - - Performance tuning + - Complex agent runtime configuration + - Difficult to troubleshoot agent failures + - Managing agent resource allocation + - Integrating governance with existing tools + - Scaling agent infrastructure content_preferences: - - Connector setup guides - - Transformation examples - - Error handling patterns - - Monitoring and troubleshooting + - Infrastructure setup guides + - Governance configuration patterns + - Observability and monitoring setup + - Performance tuning documentation + - Troubleshooting workflows typical_content_types: - how-to - - cookbook + - reference - troubleshooting + - best-practices - - id: ai_agent_developer - name: AI Agent Developer - description: Builds AI agents and integrations using MCP tools and LLM frameworks + # ============================================================================ + # TIER 3: Builders & Developers + # ============================================================================ + + - id: app_developer + name: Application Developer + description: Builds applications that produce and consume data from Redpanda Cloud experience_level: intermediate goals: + - Connect applications to Redpanda Cloud clusters + - Produce and consume messages reliably + - Implement proper error handling and retries + - Optimize client performance + pain_points: + - Authentication and connection configuration + - Understanding Kafka client options + - Debugging connectivity issues + - Choosing the right client library + content_preferences: + - Working code examples in multiple languages + - Connection configuration templates + - Client library comparisons + - Performance tuning guides + typical_content_types: + - how-to + - tutorial + - reference + + - id: agent_developer + name: Agent Developer + description: Builds AI agents, agentic workflows, and MCP tools that integrate with Redpanda Cloud and ADP + experience_level: intermediate + goals: + # MCP and streaming integration - Create MCP tools that AI assistants can discover and use - Deploy MCP servers to Redpanda Cloud - Integrate with AI/LLM applications - Debug agent-tool interactions + # Agentic workflows and governed deployment + - Build agents and workflows that solve business problems + - Use ADP catalog, templates, and curated datasets + - Design reasoning patterns and tool interactions + - Deploy agents into governed runtime pain_points: + # MCP and integration challenges - MCP configuration syntax - Testing tools before deployment - Limited AI-specific examples + # ADP and governance challenges + - Hard to discover existing templates, MCP servers, datasets + - Unclear access policies + - Brittle multi-step integrations + - Inconsistent testing/debugging environments content_preferences: + # Code examples and patterns - Working code examples with AI context - Testing and debugging workflows - Integration patterns + # Catalog and governance + - Rich catalog of agent templates and tools + - Governance introspection (what agent can/can't do) + - Replay-based debugging + - Streamlined deployment workflows typical_content_types: - tutorial - how-to @@ -127,6 +220,81 @@ personas: - reference - best-practices + # ============================================================================ + # TIER 4: Data & Knowledge Management + # ============================================================================ + + - id: data_engineer + name: Data Engineer + description: Builds data pipelines with managed connectors AND creates curated datasets for agent consumption + experience_level: intermediate + goals: + # Data movement and pipelines + - Set up managed connectors to move data between systems + - Transform and route data reliably + - Monitor connector and pipeline health + - Handle errors and retries + # Agent-ready datasets and RAG + - Create agent-ready datasets with federated SQL + - Ensure data quality and freshness for agents + - Expose data safely through governed views + - Provide clean RAG context via MCP servers + pain_points: + # Connector and pipeline challenges + - Connector configuration complexity + - Debugging failed connectors + - Schema management and evolution + - Performance tuning + # Data curation for agents + - Siloed data across sources + - Fragile RAG sources + - Schema drift + - Difficulty providing agent-ready datasets quickly + content_preferences: + # Connector and transformation + - Connector setup guides + - Transformation examples + - Error handling patterns + - Monitoring and troubleshooting + # Federated data and RAG + - Federated SQL query examples + - Governed view patterns + - RAG context design + - Data lineage visualization + typical_content_types: + - how-to + - cookbook + - troubleshooting + - reference + + - id: knowledge_manager + name: Knowledge & Operations Manager + description: Maintains organizational documentation and knowledge bases for agent consumption + experience_level: intermediate + goals: + - Ingest and maintain organizational knowledge bases + - Ensure content freshness and accuracy + - Optimize vector search for agent queries + - Manage knowledge base access and permissions + pain_points: + - Stale or outdated documentation + - Difficult to index and search content + - Managing content from multiple sources + - Ensuring agent retrieval accuracy + content_preferences: + - KB ingestion workflows + - Vector search optimization guides + - Content freshness strategies + - Access control patterns + typical_content_types: + - how-to + - best-practices + - troubleshooting + + # ============================================================================ + # TIER 5: Evaluation & End Users + # ============================================================================ + - id: evaluator name: Technical Evaluator description: Assessing Redpanda Cloud for their organization @@ -150,4 +318,27 @@ personas: - overview - concepts - tutorial - - get-started + + - id: business_user + name: Business End User + description: Uses agent-powered automations to complete business tasks + experience_level: beginner + goals: + - Complete tasks efficiently using agents + - Understand what agents can and cannot do + - Trust agent recommendations and actions + - Report issues when agents fail + pain_points: + - Unclear agent capabilities + - Unexpected agent behavior + - Lack of transparency in agent actions + - Difficulty getting help when agents fail + content_preferences: + - Simple, task-oriented guides + - Agent capability overviews + - Troubleshooting for common issues + - Trust and transparency documentation + typical_content_types: + - overview + - how-to + - troubleshooting diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index c377fa2be..7a00ca218 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -81,6 +81,7 @@ **** xref:ai-agents:agents/prompt-best-practices.adoc[System Prompt Best Practices] **** xref:ai-agents:agents/architecture-patterns.adoc[Architecture Patterns] **** xref:ai-agents:agents/troubleshooting.adoc[Troubleshoot] +**** xref:ai-agents:agents/monitor-agents.adoc[Monitor Agents] *** xref:ai-agents:agents/integration-index.adoc[Agent Integrations] **** xref:ai-agents:agents/integration-overview.adoc[Integration Patterns] **** xref:ai-agents:agents/pipeline-integration-patterns.adoc[Pipeline to Agent] @@ -96,15 +97,13 @@ **** xref:ai-agents:mcp/remote/tool-patterns.adoc[Tool Patterns] **** xref:ai-agents:mcp/remote/troubleshooting.adoc[Troubleshoot] **** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] +**** xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[Monitor MCP Servers] **** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] *** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] **** xref:ai-agents:mcp/local/overview.adoc[Overview] **** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] **** xref:ai-agents:mcp/local/configuration.adoc[Configure] -** xref:ai-agents:observability/index.adoc[Observability] -*** xref:ai-agents:observability/concepts.adoc[Concepts] -*** xref:ai-agents:observability/monitor-agents.adoc[Monitor Agents] -*** xref:ai-agents:observability/monitor-mcp-servers.adoc[Monitor MCP Servers] +** xref:ai-agents:observability/concepts.adoc[Observability] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] ** xref:develop:connect/configuration/about.adoc[] diff --git a/modules/ai-agents/pages/agents/a2a-concepts.adoc b/modules/ai-agents/pages/agents/a2a-concepts.adoc index 0cb02ecc0..1b58a4bee 100644 --- a/modules/ai-agents/pages/agents/a2a-concepts.adoc +++ b/modules/ai-agents/pages/agents/a2a-concepts.adoc @@ -2,7 +2,7 @@ :description: Learn how the A2A protocol enables agent discovery and communication. :page-topic-type: concepts :page-aliases: agents/external-app-integration.adoc -:personas: ai_agent_developer, app_developer, streaming_developer +:personas: agent_developer, app_developer, streaming_developer :learning-objective-1: Describe the A2A protocol and its role in agent communication :learning-objective-2: Explain how agent cards enable discovery :learning-objective-3: Identify how authentication secures agent communication diff --git a/modules/ai-agents/pages/agents/architecture-patterns.adoc b/modules/ai-agents/pages/agents/architecture-patterns.adoc index 146cdbc77..69097e040 100644 --- a/modules/ai-agents/pages/agents/architecture-patterns.adoc +++ b/modules/ai-agents/pages/agents/architecture-patterns.adoc @@ -1,7 +1,7 @@ = Agent Architecture Patterns :description: Design maintainable agent systems with single-agent and multi-agent patterns based on domain complexity. :page-topic-type: best-practices -:personas: ai_agent_developer, streaming_developer +:personas: agent_developer, streaming_developer :learning-objective-1: Evaluate single-agent versus multi-agent architectures for your use case :learning-objective-2: Choose appropriate LLM models based on task requirements :learning-objective-3: Apply agent boundary design principles for maintainability diff --git a/modules/ai-agents/pages/agents/concepts.adoc b/modules/ai-agents/pages/agents/concepts.adoc index f1e66fed1..5de95ceee 100644 --- a/modules/ai-agents/pages/agents/concepts.adoc +++ b/modules/ai-agents/pages/agents/concepts.adoc @@ -1,7 +1,7 @@ = Agent Concepts :description: Understand how agents execute, manage context, invoke tools, and handle errors. :page-topic-type: concepts -:personas: ai_agent_developer, streaming_developer, data_engineer +:personas: agent_developer, streaming_developer, data_engineer :learning-objective-1: Explain how agents execute reasoning loops and make tool invocation decisions :learning-objective-2: Describe how agents manage context and state across interactions :learning-objective-3: Identify error handling strategies for agent failures @@ -178,7 +178,7 @@ Redpanda uses these topics internally to persist conversation history and reload These topics and schemas are managed automatically by Redpanda. If you delete either a topic or schema, they are recreated automatically. However, deleting a topic permanently deletes all stored data (including conversation history), and the topic comes back empty. Do not produce your own data to these topics. They are reserved for agent data. -For guidance on consuming these topics, analyzing conversation history, and monitoring agent performance, see xref:ai-agents:observability/monitor-agents.adoc[]. +For guidance on consuming these topics, analyzing conversation history, and monitoring agent performance, see xref:ai-agents:agents/monitor-agents.adoc[]. === Sessions topic diff --git a/modules/ai-agents/pages/agents/create-agent.adoc b/modules/ai-agents/pages/agents/create-agent.adoc index 55f88ba15..a59a011e8 100644 --- a/modules/ai-agents/pages/agents/create-agent.adoc +++ b/modules/ai-agents/pages/agents/create-agent.adoc @@ -1,7 +1,7 @@ = Create an Agent :description: Configure agents with model selection, system prompts, tool connections, and execution parameters. :page-topic-type: how-to -:personas: ai_agent_developer, app_developer, streaming_developer +:personas: agent_developer, app_developer, streaming_developer :learning-objective-1: Configure an agent with model selection and system prompt :learning-objective-2: Connect MCP servers and select tools for your agent :learning-objective-3: Set agent execution parameters including max iterations diff --git a/modules/ai-agents/pages/agents/integration-overview.adoc b/modules/ai-agents/pages/agents/integration-overview.adoc index 51089058d..3efc49408 100644 --- a/modules/ai-agents/pages/agents/integration-overview.adoc +++ b/modules/ai-agents/pages/agents/integration-overview.adoc @@ -1,7 +1,7 @@ = Integration Patterns Overview :description: Choose the right integration pattern for connecting agents, pipelines, and external applications. :page-topic-type: best-practices -:personas: ai_agent_developer, streaming_developer, app_developer, data_engineer +:personas: agent_developer, streaming_developer, app_developer, data_engineer :learning-objective-1: Choose the integration pattern that fits your use case :learning-objective-2: Apply appropriate authentication for internal versus external integration :learning-objective-3: Select the right communication protocol for your integration scenario diff --git a/modules/ai-agents/pages/observability/monitor-agents.adoc b/modules/ai-agents/pages/agents/monitor-agents.adoc similarity index 99% rename from modules/ai-agents/pages/observability/monitor-agents.adoc rename to modules/ai-agents/pages/agents/monitor-agents.adoc index fe6a36f43..d4ea356c7 100644 --- a/modules/ai-agents/pages/observability/monitor-agents.adoc +++ b/modules/ai-agents/pages/agents/monitor-agents.adoc @@ -1,7 +1,7 @@ = Monitor Agent Activity :description: Monitor agent execution, analyze conversation history, track token usage, and debug issues using Inspector, Transcripts, and agent data topics. :page-topic-type: how-to -:personas: ai_agent_developer, platform_admin +:personas: agent_developer, platform_admin :learning-objective-1: Test agents interactively using the Inspector tab :learning-objective-2: Consume session and task topics for analysis :learning-objective-3: Debug agent behavior using Transcripts diff --git a/modules/ai-agents/pages/agents/overview.adoc b/modules/ai-agents/pages/agents/overview.adoc index 045342c9a..1e2120e1b 100644 --- a/modules/ai-agents/pages/agents/overview.adoc +++ b/modules/ai-agents/pages/agents/overview.adoc @@ -1,7 +1,7 @@ = AI Agents Overview :description: Learn what AI agents are and how Redpanda Cloud supports agent development with real-time streaming. :page-topic-type: overview -:personas: evaluator, ai_agent_developer, app_developer, streaming_developer +:personas: evaluator, agent_developer, app_developer, streaming_developer :learning-objective-1: Describe what AI agents are and their essential components :learning-objective-2: Explain how Redpanda Cloud streaming infrastructure benefits agent architectures :learning-objective-3: Identify use cases where Redpanda Cloud agents provide value diff --git a/modules/ai-agents/pages/agents/pipeline-integration-patterns.adoc b/modules/ai-agents/pages/agents/pipeline-integration-patterns.adoc index ba2d064b5..e47fa1856 100644 --- a/modules/ai-agents/pages/agents/pipeline-integration-patterns.adoc +++ b/modules/ai-agents/pages/agents/pipeline-integration-patterns.adoc @@ -1,7 +1,7 @@ = Pipeline Integration Patterns :description: Build Redpanda Connect pipelines that invoke agents for event-driven processing and streaming enrichment. :page-topic-type: best-practices -:personas: streaming_developer, ai_agent_developer +:personas: streaming_developer, agent_developer :learning-objective-1: Identify when pipelines should call agents for stream processing :learning-objective-2: pass:q[Design event-driven agent invocation using the `a2a_message` processor] :learning-objective-3: Implement streaming enrichment with AI-generated fields diff --git a/modules/ai-agents/pages/agents/prompt-best-practices.adoc b/modules/ai-agents/pages/agents/prompt-best-practices.adoc index fff63e421..7da48eb56 100644 --- a/modules/ai-agents/pages/agents/prompt-best-practices.adoc +++ b/modules/ai-agents/pages/agents/prompt-best-practices.adoc @@ -1,7 +1,7 @@ = System Prompt Best Practices :description: Write system prompts that produce reliable, predictable agent behavior through clear constraints and tool guidance. :page-topic-type: best-practices -:personas: ai_agent_developer, app_developer, streaming_developer +:personas: agent_developer, app_developer, streaming_developer :learning-objective-1: Identify effective system prompt patterns for agent reliability :learning-objective-2: Apply constraint patterns to prevent unintended agent behavior :learning-objective-3: Evaluate system prompts for clarity and completeness diff --git a/modules/ai-agents/pages/agents/quickstart.adoc b/modules/ai-agents/pages/agents/quickstart.adoc index a7e2adf58..526c1368f 100644 --- a/modules/ai-agents/pages/agents/quickstart.adoc +++ b/modules/ai-agents/pages/agents/quickstart.adoc @@ -1,7 +1,7 @@ = AI Agent Quickstart :description: Create your first AI agent in Redpanda Cloud that generates and publishes event data through natural language commands. :page-topic-type: tutorial -:personas: ai_agent_developer, evaluator +:personas: agent_developer, evaluator :learning-objective-1: Create an AI agent in Redpanda Cloud that uses MCP tools :learning-objective-2: Configure the agent with a system prompt and model selection :learning-objective-3: Test the agent by generating and publishing events through natural language diff --git a/modules/ai-agents/pages/agents/troubleshooting.adoc b/modules/ai-agents/pages/agents/troubleshooting.adoc index f8191b167..f51dbd91d 100644 --- a/modules/ai-agents/pages/agents/troubleshooting.adoc +++ b/modules/ai-agents/pages/agents/troubleshooting.adoc @@ -1,7 +1,7 @@ = Troubleshoot AI Agents :description: Diagnose and fix common issues with AI agents including deployment failures, runtime behavior problems, and tool execution errors. :page-topic-type: troubleshooting -:personas: ai_agent_developer, app_developer, streaming_developer +:personas: agent_developer, app_developer, streaming_developer :learning-objective-1: Diagnose deployment failures and resource allocation errors :learning-objective-2: Resolve runtime behavior issues including tool selection and iteration limits :learning-objective-3: Fix tool execution problems and authentication failures @@ -425,7 +425,7 @@ processors: == Monitor and debug agents -For comprehensive guidance on monitoring agent activity, analyzing conversation history, tracking token usage, and debugging issues, see xref:ai-agents:observability/monitor-agents.adoc[]. +For comprehensive guidance on monitoring agent activity, analyzing conversation history, tracking token usage, and debugging issues, see xref:ai-agents:agents/monitor-agents.adoc[]. == Next steps diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index 2968b9110..352ee08d1 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -1,7 +1,7 @@ = Learn Multi-Tool Agent Orchestration :description: Learn how agents coordinate multiple tools, make decisions based on conversation context, and handle errors through building a customer support agent. :page-topic-type: tutorial -:personas: ai_agent_developer, streaming_developer +:personas: agent_developer, streaming_developer :learning-objective-1: Explain how agents use conversation context to decide which tools to invoke :learning-objective-2: Apply tool orchestration patterns to handle multi-step workflows :learning-objective-3: Evaluate how system prompt design affects agent tool selection diff --git a/modules/ai-agents/pages/mcp/local/configuration.adoc b/modules/ai-agents/pages/mcp/local/configuration.adoc index 134c50b0c..f2170a11b 100644 --- a/modules/ai-agents/pages/mcp/local/configuration.adoc +++ b/modules/ai-agents/pages/mcp/local/configuration.adoc @@ -2,7 +2,7 @@ :page-beta: true :description: Learn how to configure the Redpanda Cloud Management MCP Server, including auto and manual client setup, enabling deletes, and security considerations. :page-topic-type: how-to -:personas: ai_agent_developer, platform_admin +:personas: agent_developer, platform_admin // Reader journey: "I customize and configure" // Learning objectives - what readers can learn from this page: :learning-objective-1: Configure MCP clients diff --git a/modules/ai-agents/pages/mcp/local/overview.adoc b/modules/ai-agents/pages/mcp/local/overview.adoc index 01bfd6227..a27e3df25 100644 --- a/modules/ai-agents/pages/mcp/local/overview.adoc +++ b/modules/ai-agents/pages/mcp/local/overview.adoc @@ -2,7 +2,7 @@ :page-beta: true :description: Learn about the Redpanda Cloud Management MCP Server, which lets AI agents securely access and operate your Redpanda Cloud account and clusters. :page-topic-type: overview -:personas: evaluator, ai_agent_developer, platform_admin +:personas: evaluator, agent_developer, platform_admin // Reader journey: "I'm new" // Learning objectives - what readers should understand after reading this page: :learning-objective-1: Explain what the Redpanda Cloud Management MCP Server does diff --git a/modules/ai-agents/pages/mcp/local/quickstart.adoc b/modules/ai-agents/pages/mcp/local/quickstart.adoc index 0408f950f..413f6d146 100644 --- a/modules/ai-agents/pages/mcp/local/quickstart.adoc +++ b/modules/ai-agents/pages/mcp/local/quickstart.adoc @@ -2,7 +2,7 @@ :page-beta: true :description: Connect your Claude AI agent to your Redpanda Cloud account and clusters using the Redpanda Cloud Management MCP Server. :page-topic-type: tutorial -:personas: ai_agent_developer, platform_admin +:personas: agent_developer, platform_admin // Reader journey: "I'm new" - seeking first hands-on experience // Learning objectives - what readers will achieve by completing this quickstart: :learning-objective-1: Authenticate to Redpanda Cloud with rpk diff --git a/modules/ai-agents/pages/mcp/overview.adoc b/modules/ai-agents/pages/mcp/overview.adoc index 5b452c357..4e4282b7a 100644 --- a/modules/ai-agents/pages/mcp/overview.adoc +++ b/modules/ai-agents/pages/mcp/overview.adoc @@ -1,7 +1,7 @@ = MCP Servers for Redpanda Cloud Overview :description: Learn about Model Context Protocol (MCP) in Redpanda Cloud, including the two complementary options: the Redpanda Cloud Management MCP Server and Remote MCP. :page-topic-type: overview -:personas: evaluator, ai_agent_developer +:personas: evaluator, agent_developer // Reader journey: "I'm new" - understanding the landscape // Learning objectives - what readers should understand after reading this page: :learning-objective-1: Describe what MCP enables for AI agents diff --git a/modules/ai-agents/pages/mcp/remote/best-practices.adoc b/modules/ai-agents/pages/mcp/remote/best-practices.adoc index 28df4084d..bf1eed69c 100644 --- a/modules/ai-agents/pages/mcp/remote/best-practices.adoc +++ b/modules/ai-agents/pages/mcp/remote/best-practices.adoc @@ -1,7 +1,7 @@ = MCP Tool Design :description: Design effective MCP tool interfaces with clear names, descriptions, and input properties. :page-topic-type: best-practices -:personas: ai_agent_developer +:personas: agent_developer // Reader journey: "I want AI clients to discover and use my tools effectively" // Learning objectives - what readers should be able to do after reading this page: :learning-objective-1: Write tool names and descriptions that help AI clients select the right tool diff --git a/modules/ai-agents/pages/mcp/remote/concepts.adoc b/modules/ai-agents/pages/mcp/remote/concepts.adoc index 678b919dd..57a366539 100644 --- a/modules/ai-agents/pages/mcp/remote/concepts.adoc +++ b/modules/ai-agents/pages/mcp/remote/concepts.adoc @@ -2,7 +2,7 @@ :description: Understand the MCP execution model, choose the right component type, and use traces for observability. :page-aliases: ai-agents:mcp/remote/understanding-mcp-tools.adoc :page-topic-type: concepts -:personas: ai_agent_developer, streaming_developer +:personas: agent_developer, streaming_developer // Reader journey: "I want to understand how it works" // Learning objectives - what readers should know after reading this page: :learning-objective-1: Describe the request/response execution model @@ -35,7 +35,7 @@ include::redpanda-connect:ai-agents:partial$mcp/concepts/component-selection.ado MCP servers automatically emit OpenTelemetry traces for monitoring and debugging. For detailed information about traces, spans, and the trace structure, see xref:ai-agents:observability/concepts.adoc[]. -To monitor MCP server activity, consume traces, and debug failures, see xref:ai-agents:observability/monitor-mcp-servers.adoc[]. +To monitor MCP server activity, consume traces, and debug failures, see xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[]. == Next steps diff --git a/modules/ai-agents/pages/mcp/remote/create-tool.adoc b/modules/ai-agents/pages/mcp/remote/create-tool.adoc index 417953a23..ef40bdde9 100644 --- a/modules/ai-agents/pages/mcp/remote/create-tool.adoc +++ b/modules/ai-agents/pages/mcp/remote/create-tool.adoc @@ -1,7 +1,7 @@ = Create an MCP Tool :description: Create an MCP tool with the correct YAML structure, metadata, and parameter mapping. :page-topic-type: how-to -:personas: ai_agent_developer, streaming_developer, data_engineer +:personas: agent_developer, streaming_developer, data_engineer // Reader journey: "I want to create a tool for my AI agent" // Learning objectives - what readers can do after reading this page: :learning-objective-1: Create a tool with the correct structure and MCP metadata diff --git a/modules/ai-agents/pages/mcp/remote/manage-servers.adoc b/modules/ai-agents/pages/mcp/remote/manage-servers.adoc index 92cbb9ba8..b1c993254 100644 --- a/modules/ai-agents/pages/mcp/remote/manage-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/manage-servers.adoc @@ -2,7 +2,7 @@ :description: Learn how to edit, stop, start, and delete MCP servers in Redpanda Cloud. :page-aliases: ai-agents:mcp/remote/admin-guide.adoc :page-topic-type: how-to -:personas: platform_admin, ai_agent_developer +:personas: platform_admin, agent_developer // Reader journey: "I operate and maintain" // Learning objectives - what readers can accomplish from this page: :learning-objective-1: Edit MCP server configurations @@ -163,5 +163,5 @@ Deletion is immediate and permanent. Make sure you have backed up any important == Next steps * xref:ai-agents:mcp/remote/scale-resources.adoc[Scale MCP server resources] to optimize performance and costs. -* xref:ai-agents:observability/monitor-mcp-servers.adoc[Monitor MCP server activity] using OpenTelemetry traces. +* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[Monitor MCP server activity] using OpenTelemetry traces. * xref:ai-agents:mcp/remote/best-practices.adoc[Learn best practices] for building robust tools. diff --git a/modules/ai-agents/pages/observability/monitor-mcp-servers.adoc b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc similarity index 98% rename from modules/ai-agents/pages/observability/monitor-mcp-servers.adoc rename to modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc index 75b0d4f4f..b0ea50962 100644 --- a/modules/ai-agents/pages/observability/monitor-mcp-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc @@ -1,7 +1,7 @@ = Monitor MCP Server Activity :description: Consume traces, track tool invocations, measure performance, and debug failures in MCP servers. :page-topic-type: how-to -:personas: platform_admin, ai_agent_developer, data_engineer +:personas: platform_admin, agent_developer, data_engineer :learning-objective-1: Consume traces from the redpanda.otel_traces topic :learning-objective-2: Track tool invocations and measure performance :learning-objective-3: Debug tool failures using trace data diff --git a/modules/ai-agents/pages/mcp/remote/overview.adoc b/modules/ai-agents/pages/mcp/remote/overview.adoc index 05b563861..8ab577444 100644 --- a/modules/ai-agents/pages/mcp/remote/overview.adoc +++ b/modules/ai-agents/pages/mcp/remote/overview.adoc @@ -1,7 +1,7 @@ = Remote MCP Server Overview :description: Discover how AI agents can interact with your streaming data and how to connect them to Redpanda Cloud. :page-topic-type: overview -:personas: evaluator, ai_agent_developer +:personas: evaluator, agent_developer // Reader journey: "I'm evaluating this" // Learning objectives - what readers should understand after reading this page: :learning-objective-1: Explain what a Remote MCP server is and how tools differ from pipelines diff --git a/modules/ai-agents/pages/mcp/remote/quickstart.adoc b/modules/ai-agents/pages/mcp/remote/quickstart.adoc index 09815fd0b..a295005d3 100644 --- a/modules/ai-agents/pages/mcp/remote/quickstart.adoc +++ b/modules/ai-agents/pages/mcp/remote/quickstart.adoc @@ -1,7 +1,7 @@ = Remote MCP Server Quickstart :description: Learn how to extend AI agents with custom tools that interact with your Redpanda data using the Model Context Protocol (MCP). :page-topic-type: tutorial -:personas: ai_agent_developer, streaming_developer, evaluator +:personas: agent_developer, streaming_developer, evaluator // Reader journey: "I want to try it now" // Learning objectives - what readers will achieve by completing this quickstart: :learning-objective-1: Create an MCP server in Redpanda Cloud diff --git a/modules/ai-agents/pages/mcp/remote/tool-patterns.adoc b/modules/ai-agents/pages/mcp/remote/tool-patterns.adoc index 3d01e1d70..edf1e0d43 100644 --- a/modules/ai-agents/pages/mcp/remote/tool-patterns.adoc +++ b/modules/ai-agents/pages/mcp/remote/tool-patterns.adoc @@ -2,7 +2,7 @@ :page-aliases: ai-agents:mcp/remote/pipeline-patterns.adoc :description: Catalog of patterns for MCP server tools in Redpanda Cloud. :page-topic-type: cookbook -:personas: ai_agent_developer, data_engineer +:personas: agent_developer, data_engineer // Reader journey: "I need an example for X" :learning-objective-1: Find reusable patterns for common MCP tool scenarios :learning-objective-2: Apply validation and error handling patterns for production robustness diff --git a/modules/ai-agents/pages/mcp/remote/troubleshooting.adoc b/modules/ai-agents/pages/mcp/remote/troubleshooting.adoc index 9c0dc41e6..51bdb1301 100644 --- a/modules/ai-agents/pages/mcp/remote/troubleshooting.adoc +++ b/modules/ai-agents/pages/mcp/remote/troubleshooting.adoc @@ -1,7 +1,7 @@ = Troubleshoot Remote MCP Servers :description: Diagnose and fix common issues when building and running Remote MCP servers in Redpanda Cloud. :page-topic-type: troubleshooting -:personas: ai_agent_developer, streaming_developer, platform_admin +:personas: agent_developer, streaming_developer, platform_admin // Reader journey: "Something went wrong" // Learning objectives - what readers can do with this page: :learning-objective-1: Diagnose and fix lint and YAML configuration errors diff --git a/modules/ai-agents/pages/observability/concepts.adoc b/modules/ai-agents/pages/observability/concepts.adoc index 461d8da86..d7818240a 100644 --- a/modules/ai-agents/pages/observability/concepts.adoc +++ b/modules/ai-agents/pages/observability/concepts.adoc @@ -1,7 +1,7 @@ = Transcripts and AI Observability :description: Understand how Redpanda captures execution traces for agents and MCP servers using OpenTelemetry. :page-topic-type: concepts -:personas: ai_agent_developer, platform_admin, data_engineer +:personas: agent_developer, platform_admin, data_engineer :learning-objective-1: Explain how traces and spans capture execution flow :learning-objective-2: Interpret trace structure for debugging and monitoring :learning-objective-3: Distinguish between observability traces and audit logs @@ -143,5 +143,5 @@ For compliance and audit requirements, use the session and task topics for agent == Next steps -* xref:ai-agents:observability/monitor-agents.adoc[]: Monitor agent execution and performance -* xref:ai-agents:observability/monitor-mcp-servers.adoc[]: Monitor MCP server activity +* xref:ai-agents:agents/monitor-agents.adoc[]: Monitor agent execution and performance +* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[]: Monitor MCP server activity diff --git a/modules/ai-agents/pages/observability/index.adoc b/modules/ai-agents/pages/observability/index.adoc deleted file mode 100644 index e9d504341..000000000 --- a/modules/ai-agents/pages/observability/index.adoc +++ /dev/null @@ -1,5 +0,0 @@ -= AI Observability -:page-layout: index -:description: Monitor and debug AI agents and MCP servers using OpenTelemetry traces, execution transcripts, and agent data topics. - -Monitor your AI agents and MCP servers with built-in observability tools that capture execution traces, conversation history, and performance metrics. From 83e3f02d8554139865bdb083157f1ececb432b07 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Wed, 21 Jan 2026 16:06:02 +0000 Subject: [PATCH 22/97] Rename to Transcripts --- modules/ROOT/nav.adoc | 4 ++-- modules/ai-agents/pages/observability/concepts.adoc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 7a00ca218..10f73a458 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -81,7 +81,7 @@ **** xref:ai-agents:agents/prompt-best-practices.adoc[System Prompt Best Practices] **** xref:ai-agents:agents/architecture-patterns.adoc[Architecture Patterns] **** xref:ai-agents:agents/troubleshooting.adoc[Troubleshoot] -**** xref:ai-agents:agents/monitor-agents.adoc[Monitor Agents] +*** xref:ai-agents:agents/monitor-agents.adoc[Monitor Agents] *** xref:ai-agents:agents/integration-index.adoc[Agent Integrations] **** xref:ai-agents:agents/integration-overview.adoc[Integration Patterns] **** xref:ai-agents:agents/pipeline-integration-patterns.adoc[Pipeline to Agent] @@ -103,7 +103,7 @@ **** xref:ai-agents:mcp/local/overview.adoc[Overview] **** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] **** xref:ai-agents:mcp/local/configuration.adoc[Configure] -** xref:ai-agents:observability/concepts.adoc[Observability] +** xref:ai-agents:observability/concepts.adoc[Transcripts] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] ** xref:develop:connect/configuration/about.adoc[] diff --git a/modules/ai-agents/pages/observability/concepts.adoc b/modules/ai-agents/pages/observability/concepts.adoc index d7818240a..9b09130ba 100644 --- a/modules/ai-agents/pages/observability/concepts.adoc +++ b/modules/ai-agents/pages/observability/concepts.adoc @@ -143,5 +143,5 @@ For compliance and audit requirements, use the session and task topics for agent == Next steps -* xref:ai-agents:agents/monitor-agents.adoc[]: Monitor agent execution and performance -* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[]: Monitor MCP server activity +* xref:ai-agents:agents/monitor-agents.adoc[] +* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] From 77429a31b38a19a968f73dc863110c4813c5cae1 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Wed, 21 Jan 2026 21:30:27 -0700 Subject: [PATCH 23/97] edit for user journey --- modules/ROOT/nav.adoc | 21 +- .../pages/ai-gateway/admin/setup-guide.adoc | 323 ++++++++++ .../builders/connect-your-agent.adoc | 555 +++++++++++++++++ .../builders/discover-gateways.adoc | 299 +++++++++ modules/ai-agents/pages/ai-gateway/index.adoc | 5 +- .../pages/ai-gateway/what-is-ai-gateway.adoc | 182 ++++++ .../AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md | 573 ++++++++++++++++++ 7 files changed, 1949 insertions(+), 9 deletions(-) create mode 100644 modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc create mode 100644 modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc create mode 100644 modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc create mode 100644 modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc create mode 100644 modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 65c8b999d..6b62b9119 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -71,14 +71,19 @@ * xref:ai-agents:index.adoc[Agentic AI] ** xref:ai-agents:ai-gateway/index.adoc[AI Gateway] -*** xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[Overview] -*** xref:ai-agents:ai-gateway/ai-gateway.adoc[Quickstart] -**** xref:ai-agents:ai-gateway/quickstart-enhanced.adoc[enhanced quickstart] -*** xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[MCP Aggregation Guide] -*** xref:ai-agents:ai-gateway/observability-logs.adoc[] -*** xref:ai-agents:ai-gateway/observability-metrics.adoc[] -*** xref:ai-agents:ai-gateway/migration-guide.adoc[Migrate] -*** xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[] +*** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[What is AI Gateway?] +*** For Administrators +**** xref:ai-agents:ai-gateway/admin/setup-guide.adoc[Setup Guide] +*** For Builders +**** xref:ai-agents:ai-gateway/builders/discover-gateways.adoc[Discover Gateways] +**** xref:ai-agents:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] +*** Reference +**** xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[Architecture Deep Dive] +**** xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[MCP Aggregation Guide] +**** xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[CEL Routing Cookbook] +**** xref:ai-agents:ai-gateway/observability-logs.adoc[Request Logs] +**** xref:ai-agents:ai-gateway/observability-metrics.adoc[Metrics and Usage] +**** xref:ai-agents:ai-gateway/migration-guide.adoc[Migration Guide] *** xref:ai-agents:ai-gateway/integrations/index.adoc[Integrations] **** Claude Code ***** xref:ai-agents:ai-gateway/integrations/claude-code-admin.adoc[Admin Guide] diff --git a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc new file mode 100644 index 000000000..150aab357 --- /dev/null +++ b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc @@ -0,0 +1,323 @@ += AI Gateway Setup Guide +:description: Complete setup guide for administrators to enable providers, configure models, create gateways, and set up routing policies. +:page-topic-type: how-to +:personas: platform_admin + +NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. + +This guide walks administrators through the complete setup process for AI Gateway, from enabling LLM providers to configuring routing policies and MCP tool aggregation. + +After completing this guide, you will be able to: + +* Enable LLM providers and models in the catalog +* Create and configure gateways with routing policies, rate limits, and spend limits +* Set up MCP tool aggregation for AI agents + +== Prerequisites + +* Access to the Redpanda Cloud Console with administrator privileges +* API keys for at least one LLM provider (OpenAI or Anthropic) +* (Optional) MCP server endpoints if you plan to use tool aggregation + +== Step 1: Enable a provider + +Providers represent upstream services (Anthropic, OpenAI) and associated credentials. Providers are disabled by default and must be enabled explicitly by an administrator. + +. In the Redpanda Cloud Console, navigate to *AI Gateway* → *Providers*. +. Select a provider (for example, Anthropic or OpenAI). +. On the *Configuration* tab for the provider, click *Add configuration*. +. Enter your API Key for the provider. ++ +TIP: Store provider API keys securely. Each provider configuration can have multiple API keys for rotation and redundancy. + +. Click *Save* to enable the provider. + +Repeat this process for each LLM provider you want to make available through AI Gateway. + +== Step 2: Enable models + +The model catalog is the set of models made available through the gateway. Models are disabled by default. After enabling a provider, you can enable its models. + +The infrastructure that serves the model differs based on the provider you select. For example, OpenAI has different reliability and availability metrics than Anthropic. When you consider all metrics, you can design your gateway to use different providers for different use cases. + +. Navigate to *AI Gateway* → *Models*. +. Review the list of available models from enabled providers. +. For each model you want to expose through gateways, toggle it to *Enabled*. ++ +Common models to enable: ++ +-- +* `openai/gpt-4o` - OpenAI's most capable model +* `openai/gpt-4o-mini` - Cost-effective OpenAI model +* `anthropic/claude-sonnet-3.5` - Balanced Anthropic model +* `anthropic/claude-opus-4` - Anthropic's most capable model +-- + +. Click *Save changes*. + +Only enabled models will be accessible through gateways. You can enable or disable models at any time without affecting existing gateways. + +=== Model naming convention + +Model requests must use the `vendor/model_id` format in the model property of the request body. This format allows AI Gateway to route requests to the appropriate provider. + +Examples: + +* `openai/gpt-4o` +* `anthropic/claude-sonnet-3.5` +* `openai/gpt-4o-mini` + +== Step 3: Create a gateway + +A gateway is a logical configuration boundary (policies + routing + observability) on top of a single deployment. It's a "virtual gateway" that you can create per team, environment (staging/production), product, or customer. + +. Navigate to *AI Gateway* → *Gateways*. +. Click *Create Gateway*. +. Configure the gateway: ++ +-- +* *Name*: Choose a descriptive name (for example, `production-gateway`, `team-ml-gateway`, `staging-gateway`) +* *Workspace*: Select the workspace this gateway belongs to ++ +TIP: A workspace is conceptually similar to a resource group in Redpanda streaming. ++ +* *Description* (optional): Add context about this gateway's purpose +* *Tags* (optional): Add metadata for organization and filtering +-- + +. Click *Create*. + +. After creation, note the following information: ++ +-- +* *Gateway ID*: Unique identifier (for example, `gw_abc123`) - users include this in the `rp-aigw-id` header +* *Gateway Endpoint*: Base URL for API requests (for example, `https://gw.ai.panda.com`) +-- + +You'll share the Gateway ID and Endpoint with users who need to access this gateway. + +== Step 4: Configure LLM routing + +On the gateway details page, select the *LLM* tab to configure rate limits, spend limits, routing, and provider pools with fallback options. + +The LLM routing pipeline visually represents the request lifecycle: + +. *Rate Limit*: Global rate limit (for example, 100 requests/second) +. *Spend Limit / Monthly Budget*: Monthly budget with blocking enforcement (for example, $15K/month) +. *Routing*: Primary provider pool with optional fallback provider pools + +=== Configure rate limits + +Rate limits control how many requests can be processed within a time window. + +. In the *LLM* tab, locate the *Rate Limit* section. +. Click *Add rate limit*. +. Configure the limit: ++ +-- +* *Requests per second*: Maximum requests per second (for example, `100`) +* *Burst allowance* (optional): Allow temporary bursts above the limit +-- + +. Click *Save*. + +Rate limits apply to all requests through this gateway, regardless of model or provider. + +=== Configure spend limits and budgets + +Spend limits prevent runaway costs by blocking requests after a monthly budget is exceeded. + +. In the *LLM* tab, locate the *Spend Limit* section. +. Click *Configure budget*. +. Set the budget: ++ +-- +* *Monthly budget*: Maximum spend per month (for example, `$15000`) +* *Enforcement*: Choose *Block* to reject requests after the budget is exceeded, or *Alert* to notify but allow requests +* *Notification threshold* (optional): Alert when X% of budget is consumed (for example, `80%`) +-- + +. Click *Save*. + +Budget tracking uses estimated costs based on token usage and public provider pricing. + +=== Configure routing and provider pools + +Provider pools define which LLM providers handle requests, with support for primary and fallback configurations. + +. In the *LLM* tab, locate the *Routing* section. +. Click *Add provider pool*. +. Configure the primary pool: ++ +-- +* *Name*: For example, `primary-anthropic` +* *Providers*: Select one or more providers (for example, Anthropic) +* *Models*: Choose which models to include (for example, `anthropic/claude-sonnet-3.5`) +* *Load balancing*: If multiple providers are selected, choose distribution strategy (round-robin, weighted, etc.) +-- + +. (Optional) Click *Add fallback pool* to configure automatic failover: ++ +-- +* *Name*: For example, `fallback-openai` +* *Providers*: Select fallback provider (for example, OpenAI) +* *Models*: Choose fallback models (for example, `openai/gpt-4o`) +* *Trigger conditions*: When to activate fallback: + ** Rate limit exceeded (429 from primary) + ** Timeout (primary provider slow) + ** Server errors (5xx from primary) +-- + +. Configure routing rules using CEL expressions (optional): ++ +For simple routing, select *Route all requests to primary pool*. ++ +For advanced routing based on request properties, use CEL expressions. See xref:ai-gateway/cel-routing-cookbook.adoc[] for examples. ++ +Example CEL expression for tier-based routing: ++ +[source,cel] +---- +request.headers["x-user-tier"] == "premium" + ? "anthropic/claude-opus-4" + : "anthropic/claude-sonnet-3.5" +---- + +. Click *Save routing configuration*. + +TIP: Provider pool (UI) = Backend pool (API) + +=== Load balancing and multi-provider distribution + +If a provider pool contains multiple providers, you can distribute traffic to balance load or optimize for cost/performance: + +* *Round-robin*: Distribute evenly across all providers +* *Weighted*: Assign weights (for example, 80% to Anthropic, 20% to OpenAI) +* *Least latency*: Route to fastest provider based on recent performance +* *Cost-optimized*: Route to cheapest provider for each model + +== Step 5: Configure MCP tools (optional) + +If your users will build AI agents that need access to tools via MCP (Model Context Protocol), configure MCP tool aggregation. + +On the gateway details page, select the *MCP* tab to configure tool discovery and execution. The MCP proxy aggregates multiple MCP servers, allowing agents to find and call tools through a single endpoint. + +=== Add MCP servers + +. In the *MCP* tab, click *Add MCP server*. +. Configure the server: ++ +-- +* *Server name*: Human-readable identifier (for example, `database-server`, `slack-server`) +* *Server URL*: Endpoint for the MCP server (for example, `https://mcp-database.example.com`) +* *Authentication*: Configure authentication if required (bearer token, API key, mTLS) +* *Enabled tools*: Select which tools from this server to expose (or *All tools*) +-- + +. Click *Test connection* to verify connectivity. +. Click *Save* to add the server to this gateway. + +Repeat for each MCP server you want to aggregate. + +=== Configure deferred tool loading + +Deferred tool loading dramatically reduces token costs by initially exposing only a search tool and orchestrator, rather than listing all available tools. + +. In the *MCP* tab, locate *Deferred Loading*. +. Toggle *Enable deferred tool loading* to *On*. +. Configure behavior: ++ +-- +* *Initially expose*: Search tool + orchestrator only +* *Load on demand*: Tools are retrieved when agents query for them +* *Token savings*: Expect 80-90% reduction in token usage for tool definitions +-- + +. Click *Save*. + +See xref:ai-gateway/mcp-aggregation-guide.adoc[] for detailed information about MCP aggregation. + +=== Configure the MCP orchestrator + +The MCP orchestrator is a built-in MCP server that enables programmatic tool calling. Agents can generate JavaScript code to call multiple tools in a single orchestrated step, reducing the number of round trips. + +Example: A workflow requiring 47 file reads can be reduced from 49 round trips to just 1 round trip using the orchestrator. + +The orchestrator is enabled by default when you enable MCP tools. You can configure: + +* *Execution timeout*: Maximum time for orchestrator workflows (for example, 30 seconds) +* *Memory limit*: Maximum memory for JavaScript execution (for example, 128MB) +* *Allowed operations*: Restrict which MCP tools can be called from orchestrator workflows + +== Verify your setup + +After completing the setup, verify that the gateway is working correctly: + +=== Test the gateway endpoint + +[source,bash] +---- +curl https://{GATEWAY_ENDPOINT}/v1/models \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" +---- + +Expected result: List of enabled models. + +=== Send a test request + +[source,bash] +---- +curl https://{GATEWAY_ENDPOINT}/v1/chat/completions \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "openai/gpt-4o-mini", + "messages": [{"role": "user", "content": "Hello, AI Gateway!"}], + "max_tokens": 50 + }' +---- + +Expected result: Successful completion response. + +=== Check observability + +. Navigate to *AI Gateway* → *Gateways* → Select your gateway → *Analytics*. +. Verify that your test request appears in the request logs. +. Check metrics: ++ +-- +* Request count: Should show your test request +* Token usage: Should show tokens consumed +* Estimated cost: Should show calculated cost +-- + +== Share access with users + +Now that your gateway is configured, share access with users (builders): + +. Provide the *Gateway ID* (for example, `gw_abc123`) +. Provide the *Gateway Endpoint* (for example, `https://gw.ai.panda.com`) +. Share API credentials (Redpanda Cloud tokens with appropriate permissions) +. (Optional) Document available models and any routing policies +. (Optional) Share rate limits and budget information + +Users can then discover and connect to the gateway using the information provided. See xref:ai-gateway/builders/discover-gateways.adoc[] for user documentation. + +== Next steps + +*Configure and optimize:* + +// * xref:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] - List, edit, and delete gateways +* xref:ai-gateway/cel-routing-cookbook.adoc[CEL Routing Cookbook] - Advanced routing patterns +// * xref:ai-gateway/admin/networking-configuration.adoc[Networking Configuration] - Configure private endpoints and connectivity + +*Monitor and observe:* + +* xref:ai-gateway/observability-metrics.adoc[Monitor Usage] - Track costs and usage across all gateways +* xref:ai-gateway/observability-logs.adoc[Request Logs] - View and filter request logs + +*Integrate tools:* + +* xref:ai-gateway/integrations/index.adoc[Integrations] - Admin guides for Claude Code, Cursor, and other tools diff --git a/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc b/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc new file mode 100644 index 000000000..44a439d11 --- /dev/null +++ b/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc @@ -0,0 +1,555 @@ += Connect Your Agent +:description: Integrate your AI agent or application with Redpanda AI Gateway for unified LLM access. +:page-topic-type: how-to +:personas: app_developer + +This guide shows you how to connect your AI agent or application to a Redpanda AI Gateway. You'll configure your client SDK, make your first request, and validate the integration. + +After completing this guide, you will be able to: + +* Configure your application to use AI Gateway with OpenAI-compatible SDKs +* Make LLM requests through the gateway and handle responses appropriately +* Validate your integration end-to-end + +== Prerequisites + +* You have discovered an available gateway and noted its Gateway ID and Endpoint ++ +If not, see xref:ai-gateway/builders/discover-gateways.adoc[]. + +* You have a Redpanda Cloud API token with access to the gateway +* You have a development environment with your chosen programming language + +== Integration overview + +Connecting to AI Gateway requires three configuration changes: + +. *Change the base URL*: Point to the gateway endpoint instead of the provider's API +. *Add authentication*: Use your Redpanda Cloud token instead of provider API keys +. *Add the gateway ID header*: Include `rp-aigw-id` to identify which gateway to use + +That's it. Your existing application code doesn't need to change. + +== Quick start + +=== Environment variables + +Set these environment variables for consistent configuration: + +[source,bash] +---- +export REDPANDA_GATEWAY_URL="https://gw.ai.panda.com" +export REDPANDA_GATEWAY_ID="gw_abc123" +export REDPANDA_API_KEY="your-redpanda-cloud-token" +---- + +Replace with your actual gateway endpoint, ID, and API token. + +=== Python (OpenAI SDK) + +[source,python] +---- +import os +from openai import OpenAI + +# Configure client to use AI Gateway +client = OpenAI( + base_url=os.getenv("REDPANDA_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_API_KEY"), + default_headers={ + "rp-aigw-id": os.getenv("REDPANDA_GATEWAY_ID") + } +) + +# Make a request (same as before) +response = client.chat.completions.create( + model="openai/gpt-4o-mini", # Note: vendor/model_id format + messages=[{"role": "user", "content": "Hello, AI Gateway!"}], + max_tokens=100 +) + +print(response.choices[0].message.content) +---- + +=== Python (Anthropic SDK) + +The Anthropic SDK can also route through AI Gateway using the OpenAI-compatible endpoint: + +[source,python] +---- +import os +from anthropic import Anthropic + +client = Anthropic( + base_url=os.getenv("REDPANDA_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_API_KEY"), + default_headers={ + "rp-aigw-id": os.getenv("REDPANDA_GATEWAY_ID") + } +) + +# Make a request +message = client.messages.create( + model="anthropic/claude-sonnet-3.5", + max_tokens=100, + messages=[{"role": "user", "content": "Hello, AI Gateway!"}] +) + +print(message.content[0].text) +---- + +=== Node.js (OpenAI SDK) + +[source,javascript] +---- +import OpenAI from 'openai'; + +const openai = new OpenAI({ + baseURL: process.env.REDPANDA_GATEWAY_URL, + apiKey: process.env.REDPANDA_API_KEY, + defaultHeaders: { + 'rp-aigw-id': process.env.REDPANDA_GATEWAY_ID + } +}); + +// Make a request +const response = await openai.chat.completions.create({ + model: 'openai/gpt-4o-mini', + messages: [{ role: 'user', content: 'Hello, AI Gateway!' }], + max_tokens: 100 +}); + +console.log(response.choices[0].message.content); +---- + +=== cURL + +For testing or shell scripts: + +[source,bash] +---- +curl ${REDPANDA_GATEWAY_URL}/v1/chat/completions \ + -H "Authorization: Bearer ${REDPANDA_API_KEY}" \ + -H "rp-aigw-id: ${REDPANDA_GATEWAY_ID}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "openai/gpt-4o-mini", + "messages": [{"role": "user", "content": "Hello, AI Gateway!"}], + "max_tokens": 100 + }' +---- + +== Model naming convention + +When making requests through AI Gateway, use the `vendor/model_id` format for the model parameter: + +* `openai/gpt-4o` +* `openai/gpt-4o-mini` +* `anthropic/claude-sonnet-3.5` +* `anthropic/claude-opus-4` + +This format tells AI Gateway which provider to route the request to. + +Example: + +[source,python] +---- +# Route to OpenAI +response = client.chat.completions.create( + model="openai/gpt-4o", + messages=[...] +) + +# Route to Anthropic (same client, different model) +response = client.chat.completions.create( + model="anthropic/claude-sonnet-3.5", + messages=[...] +) +---- + +// To see which models are available in your gateway, see xref:ai-gateway/builders/available-models.adoc[]. + +== Handle responses + +Responses from AI Gateway follow the OpenAI API format: + +[source,python] +---- +response = client.chat.completions.create( + model="openai/gpt-4o-mini", + messages=[{"role": "user", "content": "Explain AI Gateway"}], + max_tokens=200 +) + +# Access the response +message_content = response.choices[0].message.content +finish_reason = response.choices[0].finish_reason # 'stop', 'length', etc. + +# Token usage +prompt_tokens = response.usage.prompt_tokens +completion_tokens = response.usage.completion_tokens +total_tokens = response.usage.total_tokens + +print(f"Response: {message_content}") +print(f"Tokens: {prompt_tokens} prompt + {completion_tokens} completion = {total_tokens} total") +---- + +== Handle errors + +AI Gateway returns standard HTTP status codes: + +[source,python] +---- +from openai import OpenAI, OpenAIError + +client = OpenAI( + base_url=os.getenv("REDPANDA_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_API_KEY"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_GATEWAY_ID")} +) + +try: + response = client.chat.completions.create( + model="openai/gpt-4o-mini", + messages=[{"role": "user", "content": "Hello"}] + ) + print(response.choices[0].message.content) + +except OpenAIError as e: + if e.status_code == 400: + print("Bad request - check model name and parameters") + elif e.status_code == 401: + print("Authentication failed - check API token") + elif e.status_code == 404: + print("Model not found - check available models") + elif e.status_code == 429: + print("Rate limit exceeded - slow down requests") + elif e.status_code >= 500: + print("Gateway or provider error - retry with exponential backoff") + else: + print(f"Error: {e}") +---- + +Common error codes: + +* *400*: Bad request (invalid parameters, malformed JSON) +* *401*: Authentication failed (invalid or missing API token) +* *403*: Forbidden (no access to this gateway) +* *404*: Model not found (model not enabled in gateway) +* *429*: Rate limit exceeded (too many requests) +* *500/502/503*: Server error (gateway or provider issue) + +== Streaming responses + +AI Gateway supports streaming for real-time token generation: + +[source,python] +---- +response = client.chat.completions.create( + model="openai/gpt-4o-mini", + messages=[{"role": "user", "content": "Write a short poem"}], + stream=True # Enable streaming +) + +# Process chunks as they arrive +for chunk in response: + if chunk.choices[0].delta.content: + print(chunk.choices[0].delta.content, end='', flush=True) + +print() # New line after streaming completes +---- + +== Switch between providers + +One of AI Gateway's key benefits is easy provider switching without code changes: + +[source,python] +---- +# Try OpenAI +response = client.chat.completions.create( + model="openai/gpt-4o", + messages=[{"role": "user", "content": "Explain quantum computing"}] +) + +# Try Anthropic (same code, different model) +response = client.chat.completions.create( + model="anthropic/claude-sonnet-3.5", + messages=[{"role": "user", "content": "Explain quantum computing"}] +) +---- + +Compare responses, latency, and cost to determine the best model for your use case. + +== Validate your integration + +=== Test connectivity + +[source,python] +---- +import os +from openai import OpenAI + +def test_gateway_connection(): + """Test basic connectivity to AI Gateway""" + client = OpenAI( + base_url=os.getenv("REDPANDA_GATEWAY_URL"), + api_key=os.getenv("REDPANDA_API_KEY"), + default_headers={"rp-aigw-id": os.getenv("REDPANDA_GATEWAY_ID")} + ) + + try: + # Simple test request + response = client.chat.completions.create( + model="openai/gpt-4o-mini", + messages=[{"role": "user", "content": "test"}], + max_tokens=10 + ) + print("✓ Gateway connection successful") + return True + except Exception as e: + print(f"✗ Gateway connection failed: {e}") + return False + +if __name__ == "__main__": + test_gateway_connection() +---- + +=== Test multiple models + +[source,python] +---- +def test_models(): + """Test multiple models through the gateway""" + models = [ + "openai/gpt-4o-mini", + "anthropic/claude-sonnet-3.5" + ] + + for model in models: + try: + response = client.chat.completions.create( + model=model, + messages=[{"role": "user", "content": "Say hello"}], + max_tokens=10 + ) + print(f"✓ {model}: {response.choices[0].message.content}") + except Exception as e: + print(f"✗ {model}: {e}") +---- + +=== Check request logs + +After making requests, verify they appear in observability: + +. Navigate to *AI Gateway* → *Gateways* → Select your gateway → *Logs* +. Filter by your request timestamp +. Verify your requests are logged with correct model, tokens, and cost + +// See xref:ai-gateway/builders/monitor-your-usage.adoc[] for details. + +== Integrate with AI development tools + +=== Claude Code + +Configure Claude Code to use AI Gateway: + +[source,bash] +---- +claude mcp add --transport http redpanda-aigateway https://gw.ai.panda.com/mcp \ + --header "Authorization: Bearer ${REDPANDA_API_KEY}" \ + --header "rp-aigw-id: ${REDPANDA_GATEWAY_ID}" +---- + +Or edit `~/.claude/config.json`: + +[source,json] +---- +{ + "mcpServers": { + "redpanda-ai-gateway": { + "transport": "http", + "url": "https://gw.ai.panda.com/mcp", + "headers": { + "Authorization": "Bearer your-api-key", + "rp-aigw-id": "gw_abc123" + } + } + } +} +---- + +See xref:ai-gateway/integrations/claude-code-user.adoc[] for complete setup. + +=== VS Code Continue Extension + +Edit `~/.continue/config.json`: + +[source,json] +---- +{ + "models": [ + { + "title": "AI Gateway - GPT-4", + "provider": "openai", + "model": "openai/gpt-4o", + "apiBase": "https://gw.ai.panda.com", + "apiKey": "your-redpanda-api-key", + "requestOptions": { + "headers": { + "rp-aigw-id": "gw_abc123" + } + } + } + ] +} +---- + +See xref:ai-gateway/integrations/continue-user.adoc[] for complete setup. + +=== Cursor IDE + +. Open Cursor Settings (*Cursor* → *Settings* or `Cmd+,`) +. Navigate to *AI* settings +. Add custom OpenAI-compatible provider: + +[source,json] +---- +{ + "cursor.ai.providers.openai.apiBase": "https://gw.ai.panda.com", + "cursor.ai.providers.openai.defaultHeaders": { + "rp-aigw-id": "gw_abc123" + } +} +---- + +See xref:ai-gateway/integrations/cursor-user.adoc[] for complete setup. + +== Best practices + +=== Use environment variables + +Store configuration in environment variables, not hardcoded in code: + +[source,python] +---- +# Good +base_url = os.getenv("REDPANDA_GATEWAY_URL") + +# Bad +base_url = "https://gw.ai.panda.com" # Don't hardcode +---- + +=== Implement retry logic + +Implement exponential backoff for transient errors: + +[source,python] +---- +import time +from openai import OpenAI, OpenAIError + +def make_request_with_retry(client, max_retries=3): + for attempt in range(max_retries): + try: + return client.chat.completions.create( + model="openai/gpt-4o-mini", + messages=[{"role": "user", "content": "Hello"}] + ) + except OpenAIError as e: + if e.status_code >= 500 and attempt < max_retries - 1: + wait_time = 2 ** attempt # Exponential backoff + print(f"Retrying in {wait_time}s...") + time.sleep(wait_time) + else: + raise +---- + +=== Monitor your usage + +Regularly check your usage to avoid unexpected costs: + +[source,python] +---- +# Track tokens in your application +total_tokens = 0 +request_count = 0 + +for request in requests: + response = client.chat.completions.create(...) + total_tokens += response.usage.total_tokens + request_count += 1 + +print(f"Total tokens: {total_tokens} across {request_count} requests") +---- + +// See xref:ai-gateway/builders/monitor-your-usage.adoc[] for detailed monitoring. + +=== Handle rate limits gracefully + +Respect rate limits and implement backoff: + +[source,python] +---- +try: + response = client.chat.completions.create(...) +except OpenAIError as e: + if e.status_code == 429: + # Rate limited - wait and retry + retry_after = int(e.response.headers.get('Retry-After', 60)) + print(f"Rate limited. Waiting {retry_after}s...") + time.sleep(retry_after) + # Retry request +---- + +== Troubleshooting + +=== "Authentication failed" + +Problem: 401 Unauthorized + +Solutions: + +* Verify your API token is correct and not expired +* Check that the token has access to the specified gateway +* Ensure the `Authorization` header is formatted correctly: `Bearer ` + +=== "Model not found" + +Problem: 404 Model not found + +Solutions: + +* Verify the model name uses `vendor/model_id` format +// * Check available models: See xref:ai-gateway/builders/available-models.adoc[] +* Confirm the model is enabled in your gateway (contact administrator) + +=== "Rate limit exceeded" + +Problem: 429 Too Many Requests + +Solutions: + +* Reduce request rate +* Implement exponential backoff +* Contact administrator to review rate limits +* Consider using a different gateway if available + +=== "Connection timeout" + +Problem: Request times out + +Solutions: + +* Check network connectivity to the gateway endpoint +* Verify the gateway endpoint URL is correct +* Check if the gateway is operational (contact administrator) +* Increase client timeout if processing complex requests + +== Next steps + +Now that your agent is connected: + +// * xref:ai-gateway/builders/available-models.adoc[Available Models] - Learn about model selection and routing +// * xref:ai-gateway/builders/use-mcp-tools.adoc[Use MCP Tools] - Access tools from MCP servers (if enabled) +// * xref:ai-gateway/builders/monitor-your-usage.adoc[Monitor Your Usage] - Track requests and costs +* xref:ai-gateway/integrations/index.adoc[Integrations] - Configure specific tools and IDEs diff --git a/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc b/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc new file mode 100644 index 000000000..43890fa5f --- /dev/null +++ b/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc @@ -0,0 +1,299 @@ += Discover Available Gateways +:description: Find which AI Gateways you can access and their configurations. +:page-topic-type: how-to +:personas: app_developer + +As a builder, you need to know which gateways are available to you before integrating your agent or application. This page shows you how to discover accessible gateways, understand their configurations, and verify connectivity. + +After reading this page, you will be able to: + +* List all AI Gateways you have access to and retrieve their endpoints and IDs +* View which models and MCP tools are available through each gateway +* Test gateway connectivity before integration + +== Before you begin + +* You have a Redpanda Cloud account with access to at least one AI Gateway +* You have access to the Redpanda Cloud Console or API credentials + +== List your accessible gateways + +=== Using the Console + +. Navigate to *AI Gateway* in the Redpanda Cloud Console. +. View the *My Gateways* tab (or *Gateways* if you're an administrator). +. Review the list of gateways you can access: ++ +For each gateway, you'll see: ++ +-- +* *Gateway Name*: Human-readable name (for example, `production-gateway`, `team-ml-gateway`) +* *Gateway ID*: Unique identifier used in the `rp-aigw-id` header (for example, `gw_abc123`) +* *Gateway Endpoint*: Base URL for API requests (for example, `https://gw.ai.panda.com`) +* *Status*: Whether the gateway is active and accepting requests +* *Available Models*: Which LLM models you can access +* *MCP Tools*: Whether MCP tool aggregation is enabled +-- + +=== Using the API + +You can also list gateways programmatically: + +[source,bash] +---- +curl https://api.redpanda.com/v1/ai-gateway/gateways \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" +---- + +Response: + +[source,json] +---- +{ + "gateways": [ + { + "id": "gw_abc123", + "name": "production-gateway", + "endpoint": "https://gw.ai.panda.com", + "status": "active", + "workspace_id": "ws_xyz789", + "created_at": "2025-01-15T10:30:00Z" + }, + { + "id": "gw_def456", + "name": "staging-gateway", + "endpoint": "https://gw-staging.ai.panda.com", + "status": "active", + "workspace_id": "ws_xyz789", + "created_at": "2025-01-10T08:15:00Z" + } + ] +} +---- + +== Understand gateway information + +Each gateway provides specific information you'll need for integration: + +=== Gateway ID + +The Gateway ID is a unique identifier that you include in the `rp-aigw-id` header with every request. This tells AI Gateway which gateway configuration to use for routing, policies, and observability. + +Example: +[source,bash] +---- +rp-aigw-id: gw_abc123 +---- + +=== Gateway Endpoint + +The endpoint is the base URL where you send all API requests. This replaces direct provider URLs (like `api.openai.com` or `api.anthropic.com`). + +Example: +[source,bash] +---- +https://gw.ai.panda.com +---- + +Your application configures this as the `base_url` in your SDK client. + +=== Available Models + +Each gateway exposes specific models based on administrator configuration. Models use the `vendor/model_id` format: + +* `openai/gpt-4o` +* `anthropic/claude-sonnet-3.5` +* `openai/gpt-4o-mini` + +To see which models are available through a specific gateway: + +[source,bash] +---- +curl https://{GATEWAY_ENDPOINT}/v1/models \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" +---- + +Response: + +[source,json] +---- +{ + "object": "list", + "data": [ + { + "id": "openai/gpt-4o", + "object": "model", + "owned_by": "openai" + }, + { + "id": "anthropic/claude-sonnet-3.5", + "object": "model", + "owned_by": "anthropic" + }, + { + "id": "openai/gpt-4o-mini", + "object": "model", + "owned_by": "openai" + } + ] +} +---- + +=== Rate Limits and Quotas + +Each gateway may have configured rate limits and monthly budgets. Check the console or contact your administrator to understand: + +* Requests per minute/hour/day +* Monthly spend limits +* Token usage quotas + +These limits help control costs and ensure fair resource allocation across teams. + +=== MCP Tools + +If MCP aggregation is enabled for your gateway, you can access tools from multiple MCP servers through a single endpoint. + +To discover available MCP tools: + +[source,bash] +---- +curl https://{GATEWAY_ENDPOINT}/mcp/tools \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" \ + -H "rp-aigw-mcp-deferred: true" +---- + +With deferred loading enabled, you'll receive search and orchestrator tools initially. You can then query for specific tools as needed. + +// See xref:ai-gateway/builders/use-mcp-tools.adoc[] for more details. + +== Check gateway availability + +Before integrating your application, verify that you can successfully connect to the gateway: + +=== Test connectivity + +[source,bash] +---- +curl https://{GATEWAY_ENDPOINT}/v1/models \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" \ + -v +---- + +Expected result: HTTP 200 response with a list of available models. + +=== Test a simple request + +Send a minimal chat completion request to verify end-to-end functionality: + +[source,bash] +---- +curl https://{GATEWAY_ENDPOINT}/v1/chat/completions \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "openai/gpt-4o-mini", + "messages": [{"role": "user", "content": "Hello"}], + "max_tokens": 10 + }' +---- + +Expected result: HTTP 200 response with a completion. + +=== Troubleshoot connectivity issues + +If you cannot connect to a gateway: + +. *Verify authentication*: Ensure your API token is valid and has not expired +. *Check gateway ID*: Confirm you're using the correct `rp-aigw-id` value +. *Verify endpoint URL*: Check for typos in the gateway endpoint +. *Check permissions*: Confirm with your administrator that you have access to this gateway +. *Review network connectivity*: Ensure your network allows outbound HTTPS connections + +== Choose the right gateway + +If you have access to multiple gateways, consider which one to use based on your needs: + +=== By environment + +Organizations often create separate gateways for different environments: + +* *Production gateway*: Higher rate limits, access to all models, monitoring enabled +* *Staging gateway*: Lower rate limits, restricted models, aggressive cost controls +* *Development gateway*: Minimal limits, all models for experimentation + +Choose the gateway that matches your deployment environment. + +=== By team or project + +Gateways may be organized by team or project for cost tracking and isolation: + +* *team-ml-gateway*: For machine learning team +* *team-product-gateway*: For product team +* *customer-facing-gateway*: For production customer workloads + +Use the gateway designated for your team to ensure proper cost attribution. + +=== By capability + +Different gateways may have different features enabled: + +* *Gateway with MCP tools*: Use if your agent needs to call tools +* *Gateway without MCP*: Use for simple LLM completions +* *Gateway with specific models*: Use if you need access to particular models + +== Example: Complete discovery workflow + +Here's a complete workflow to discover and validate gateway access: + +[source,bash] +---- +#!/bin/bash + +# Set your API token +export REDPANDA_CLOUD_TOKEN="your-token-here" + +# Step 1: List all accessible gateways +echo "=== Discovering gateways ===" +curl -s https://api.redpanda.com/v1/ai-gateway/gateways \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + | jq '.gateways[] | {name: .name, id: .id, endpoint: .endpoint}' + +# Step 2: Select a gateway (example) +export GATEWAY_ID="gw_abc123" +export GATEWAY_ENDPOINT="https://gw.ai.panda.com" + +# Step 3: List available models +echo -e "\n=== Available models ===" +curl -s ${GATEWAY_ENDPOINT}/v1/models \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" \ + | jq '.data[] | .id' + +# Step 4: Test with a simple request +echo -e "\n=== Testing request ===" +curl -s ${GATEWAY_ENDPOINT}/v1/chat/completions \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "openai/gpt-4o-mini", + "messages": [{"role": "user", "content": "Say hello"}], + "max_tokens": 10 + }' \ + | jq '.choices[0].message.content' + +echo -e "\n=== Gateway validated successfully ===" +---- + +== Next steps + +Now that you've discovered your available gateways: + +* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] - Integrate your application +// * xref:ai-gateway/builders/available-models.adoc[Available Models] - Learn about model selection and routing +// * xref:ai-gateway/builders/use-mcp-tools.adoc[Use MCP Tools] - Access tools from MCP servers +// * xref:ai-gateway/builders/monitor-your-usage.adoc[Monitor Your Usage] - Track requests and costs diff --git a/modules/ai-agents/pages/ai-gateway/index.adoc b/modules/ai-agents/pages/ai-gateway/index.adoc index a84ffbf2a..2a3d3ebca 100644 --- a/modules/ai-agents/pages/ai-gateway/index.adoc +++ b/modules/ai-agents/pages/ai-gateway/index.adoc @@ -1,3 +1,6 @@ = AI Gateway -:description: Learn how to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. +:description: Unified access layer for LLM providers and AI tools with centralized routing, policy enforcement, cost management, and observability. :page-layout: index +:personas: platform_admin, app_developer, evaluator + +Redpanda AI Gateway provides a unified access layer for LLM providers and AI tools that sits between your applications and the AI services they use. It delivers centralized routing, policy enforcement, cost management, and observability for all your AI traffic. \ No newline at end of file diff --git a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc new file mode 100644 index 000000000..d884f4a59 --- /dev/null +++ b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc @@ -0,0 +1,182 @@ += What is an AI Gateway? +:description: Understand what an AI Gateway is, the problems it solves, and how it benefits your AI infrastructure. +:page-topic-type: concept +:personas: app_developer, platform_admin + +NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. + +Redpanda AI Gateway is a unified access layer for LLM providers and AI tools that sits between your applications and the AI services they use. It provides centralized routing, policy enforcement, cost management, and observability for all your AI traffic. + +After reading this page, you will be able to: + +* Describe how AI Gateway centralizes LLM provider management and reduces operational complexity +* Identify key features (routing, observability, cost controls) that address common LLM integration challenges +* Determine whether AI Gateway fits your use case based on traffic volume and provider diversity + +== The problem + +Modern AI applications face four critical challenges that increase costs, reduce reliability, and slow down development. + +First, applications typically hardcode provider-specific SDKs. An application using OpenAI's SDK cannot easily switch to Anthropic or Google without code changes and redeployment. This tight coupling makes testing across providers time-consuming and error-prone, and means provider outages directly impact your application availability. + +Second, costs can spiral without visibility into usage patterns. Without a centralized view of token consumption across teams and applications, it's difficult to attribute costs to specific customers, features, or environments. Testing and debugging can generate unexpected bills, and there's no way to enforce budgets or rate limits per team or customer. + +Third, AI agents that use MCP (Model Context Protocol) servers face tool coordination challenges. Managing tool discovery and execution is repetitive across projects, and agents typically load all available tools upfront, which creates high token costs. There's also no centralized governance over which tools agents can access. + +Finally, observability is fragmented across provider dashboards. You cannot reconstruct user sessions that span multiple models, compare latency and costs across providers in a unified view, or efficiently debug issues. Troubleshooting "the AI gave the wrong answer" requires manual log diving across different systems. + +== What AI Gateway solves + +Redpanda AI Gateway addresses these challenges through four core capabilities: + +=== 1. Unified LLM access (single endpoint for all providers) + +AI Gateway provides a single OpenAI-compatible endpoint that routes requests to multiple LLM providers. Instead of integrating with each provider's SDK separately, you configure your application once and switch providers by changing only the model parameter. + +Without AI Gateway, you need different SDKs and patterns for each provider: + +[source,python] +---- +# OpenAI +from openai import OpenAI +client = OpenAI(api_key="sk-...") +response = client.chat.completions.create( + model="gpt-4o", + messages=[{"role": "user", "content": "Hello"}] +) + +# Anthropic (different SDK, different patterns) +from anthropic import Anthropic +client = Anthropic(api_key="sk-ant-...") +response = client.messages.create( + model="claude-sonnet-3.5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}] +) +---- + +With AI Gateway, you use the OpenAI SDK for all providers: + +[source,python] +---- +from openai import OpenAI + +# Single configuration, multiple providers +client = OpenAI( + base_url="https://{GATEWAY_ENDPOINT}", + api_key="your-redpanda-token", + default_headers={"rp-aigw-id": "{GATEWAY_ID}"} +) + +# Route to OpenAI +response = client.chat.completions.create( + model="openai/gpt-4o", + messages=[{"role": "user", "content": "Hello"}] +) + +# Route to Anthropic (same code, different model string) +response = client.chat.completions.create( + model="anthropic/claude-sonnet-3.5", + messages=[{"role": "user", "content": "Hello"}] +) +---- + +To switch providers, you change only the `model` parameter from `openai/gpt-4o` to `anthropic/claude-sonnet-3.5`. No code changes or redeployment needed. + +=== 2. Policy-based routing and cost control + +AI Gateway lets you define routing rules, rate limits, and budgets once, then enforces them automatically for all requests. + +You can route requests to different models based on user attributes. For example, to direct premium users to a more capable model while routing free tier users to a cost-effective option, use a CEL expression: + +[source,cel] +---- +// Route premium users to best model, free users to cost-effective model +request.headers["x-user-tier"] == "premium" + ? "anthropic/claude-opus-4" + : "anthropic/claude-sonnet-3.5" +---- + +You can also set different rate limits and spend limits per environment to prevent staging or development traffic from consuming production budgets. + +For reliability, you can configure provider pools with automatic failover. If you configure OpenAI GPT-4 as your primary model and Anthropic Claude Opus as the fallback, the gateway automatically routes requests to the fallback when it detects rate limits or timeouts from the primary provider. This configuration can achieve 99.9% uptime even during provider outages. + +=== 3. MCP aggregation and orchestration + +AI Gateway aggregates multiple MCP (Model Context Protocol) servers and provides deferred tool loading, which dramatically reduces token costs for AI agents. + +Without AI Gateway, agents typically load all available tools from multiple MCP servers at startup. This approach sends 50+ tool definitions with every request, creating high token costs (thousands of tokens per request), slow agent startup times, and no centralized governance over which tools agents can access. + +With AI Gateway, you configure approved MCP servers once, and the gateway loads only search and orchestrator tools initially. Agents query for specific tools only when needed, which reduces token usage by 80-90% depending on your configuration. You also gain centralized approval and governance over which MCP servers your agents can access. + +For complex workflows, AI Gateway provides a JavaScript-based orchestrator tool that reduces multi-step workflows from multiple round trips to a single call. For example, you can create a workflow that searches a vector database and, if the results are insufficient, falls back to web search—all in one orchestration step. + +=== 4. Unified observability and cost tracking + +AI Gateway provides a single dashboard that tracks all LLM traffic across providers, eliminating the need to switch between multiple provider dashboards. + +The dashboard tracks request volume per gateway, model, and provider, along with token usage for both prompt and completion tokens. You can view estimated spend per model with cross-provider comparisons, latency metrics (p50, p95, p99), and errors broken down by type, provider, and model. + +This unified view helps you answer critical questions such as which model is the most cost-effective for your use case, why a specific user request failed, how much your staging environment costs per week, and what the latency difference is between providers for your workload. + +== Common gateway patterns + +=== Team isolation + +When multiple teams share infrastructure but need separate budgets and policies, create one gateway per team. For example, you might configure Team A's gateway with a $5K/month budget for both staging and production environments, while Team B's gateway has a $10K/month budget with different rate limits. Each team sees only their own traffic in the observability dashboards, providing clear cost attribution and isolation. + +=== Environment separation + +To prevent staging traffic from affecting production metrics, create separate gateways for each environment. Configure the staging gateway with lower rate limits, restricted model access, and aggressive cost controls to prevent runaway expenses. The production gateway can have higher rate limits, access to all models, and alerting configured to detect anomalies. + +=== Primary and fallback for reliability + +To ensure uptime during provider outages, configure provider pools with automatic failover. For example, you can set OpenAI as your primary provider (preferred for quality) and configure Anthropic as the fallback that activates when the gateway detects rate limits or timeouts from OpenAI. Monitor the fallback rate to detect primary provider issues early, before they impact your users. + +=== A/B testing models + +To compare model quality and cost without dual integration, route a percentage of traffic to different models. For example, you can send 80% of traffic to `claude-sonnet-3.5` and 20% to `claude-opus-4`, then compare quality metrics and costs in the observability dashboard before adjusting the split. + +=== Customer-based routing + +For SaaS products with tiered pricing (free, pro, enterprise), use CEL routing based on request headers to match users with appropriate models: + +[source,cel] +---- +request.headers["x-customer-tier"] == "enterprise" ? "anthropic/claude-opus-4" : +request.headers["x-customer-tier"] == "pro" ? "anthropic/claude-sonnet-3.5" : +"anthropic/claude-haiku" +---- + +== When to use AI Gateway + +AI Gateway is ideal for organizations that: + +* Use or plan to use multiple LLM providers +* Need centralized cost tracking and budgeting +* Want to experiment with different models without code changes +* Require high availability during provider outages +* Have multiple teams or customers using AI services +* Build AI agents that need MCP tool aggregation +* Need unified observability across all AI traffic + +AI Gateway may not be necessary if: + +* You only use a single provider with simple requirements +* You have minimal AI traffic (< 1000 requests/day) +* You don't need cost tracking or policy enforcement +* Your application doesn't require provider switching + +== Next steps + +Now that you understand what AI Gateway is and how it can benefit your organization: + +*For Administrators:* + +* xref:ai-gateway/admin/setup-guide.adoc[Setup Guide] - Enable providers, models, and create gateways +* xref:ai-gateway/ai-gateway-overview.adoc[Architecture Deep Dive] - Technical architecture details + +*For Builders:* + +* xref:ai-gateway/builders/discover-gateways.adoc[Discover Available Gateways] - Find which gateways you can access +* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] - Integrate your application diff --git a/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md b/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md new file mode 100644 index 000000000..3811c26b5 --- /dev/null +++ b/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md @@ -0,0 +1,573 @@ +# AI Gateway Content Restructuring Plan +## Persona-Based Reorganization + +**Date:** January 21, 2026 +**Purpose:** Restructure AI Gateway documentation to align with two primary personas (Admins and Builders) and their distinct user journeys. + +--- + +## Executive Summary + +The current AI Gateway documentation is comprehensive but doesn't clearly distinguish between Admin and Builder personas. This plan proposes: + +1. **Restructure the navigation** to create clear persona-based paths +2. **Create new landing/discovery pages** for each persona +3. **Tag existing content** with appropriate personas +4. **Add missing content** to complete user journeys +5. **Reorganize the index** to guide users based on their role + +--- + +## Personas Defined + +### Admin Persona +- **Role:** Platform administrators with broad oversight +- **Responsibilities:** + - Configure system-level parameters + - Enable/disable LLM providers and models + - Set up gateways with policies, routing, and budgets + - Monitor usage across the organization + - Manage access control and security +- **Key Questions:** + - How do I set up and configure AI Gateway for my organization? + - How do I control costs and enforce policies? + - How do I monitor usage across all teams? + +### Builder Persona +- **Role:** Developers/engineers building agents or AI applications +- **Responsibilities:** + - Build agents and AI applications + - Integrate agents with available gateways + - Use MCP tools and services + - Monitor their own usage and costs +- **Key Questions:** + - Which gateways can I use? + - How do I connect my agent to a gateway? + - What tools/models are available to me? + - How much am I spending? + +--- + +## User Journey Mapping + +### Admin User Journey +1. **Understand** → What is an AI gateway? (conceptual) +2. **Set Up** → Enable providers, enable models, create gateways +3. **Configure** → Set up networking, policies, routing, budgets +4. **Monitor** → Track usage, costs, and manage access +5. **Optimize** → Adjust policies, routing, and costs based on metrics + +### Builder User Journey +1. **Discover** → Which gateways can I access? +2. **Connect** → How do I integrate my agent with a gateway? +3. **Build** → Use available models and MCP tools +4. **Test** → Validate my agent's integration +5. **Monitor** → Track my usage and costs + +--- + +## Content Gap Analysis + +### Missing Content +| Content Needed | Persona | Priority | Current Status | +|---------------|---------|----------|----------------| +| Gateway Discovery page | Builder | HIGH | Missing - critical for Builder journey | +| "What is AI Gateway" standalone page | Both | HIGH | Content exists in overview but needs extraction | +| Admin Setup Guide | Admin | HIGH | Scattered across quickstart - needs consolidation | +| Builder Integration Guide | Builder | HIGH | Exists partially in quickstart/integrations | +| Networking Configuration page | Admin | MEDIUM | Mentioned but not detailed | +| Access Management page | Admin | MEDIUM | Missing | + +### Existing Content Gaps +1. **ai-gateway-overview.adoc** - Too dense, mixes Admin and Builder concerns +2. **ai-gateway.adoc (quickstart)** - Conflates Admin setup with Builder usage +3. **index.adoc** - Too minimal, provides no guidance +4. **No discovery mechanism** - Builders don't know which gateways they can use + +--- + +## Recommended Content Structure + +### New Navigation Structure + +``` +AI Gateway/ +├── index.adoc (New: Persona-based landing page) +├── what-is-ai-gateway.adoc (New: Extracted from overview) +│ +├── For Admins/ +│ ├── admin-overview.adoc (New: Admin-focused overview) +│ ├── setup-guide.adoc (New: Complete admin setup) +│ │ ├── enable-providers.adoc (Extracted from quickstart) +│ │ ├── enable-models.adoc (Extracted from quickstart) +│ │ ├── create-gateways.adoc (Extracted from quickstart) +│ │ ├── networking-configuration.adoc (New/Expanded) +│ ├── configure-policies.adoc (Consolidated) +│ │ ├── routing-policies.adoc (Link to CEL cookbook) +│ │ ├── access-controls.adoc (New) +│ │ ├── budgets-and-limits.adoc (Consolidated from quickstart) +│ ├── manage-gateways.adoc (New: List, edit, delete) +│ ├── observability-admin.adoc (Link to metrics dashboard) +│ └── integrations/ (Admin versions) +│ ├── index.adoc +│ ├── claude-code-admin.adoc +│ ├── cursor-admin.adoc +│ └── ... +│ +├── For Builders/ +│ ├── builder-overview.adoc (New: Builder-focused overview) +│ ├── discover-gateways.adoc (NEW - CRITICAL) +│ ├── connect-your-agent.adoc (New: Integration guide) +│ ├── available-models.adoc (New: How to see what's available) +│ ├── use-mcp-tools.adoc (Link to MCP aggregation) +│ ├── test-your-integration.adoc (New: Validation) +│ ├── monitor-your-usage.adoc (Link to observability-logs) +│ └── integrations/ (Builder versions) +│ ├── index.adoc +│ ├── claude-code-user.adoc +│ ├── cursor-user.adoc +│ └── ... +│ +├── Reference/ +│ ├── ai-gateway-overview.adoc (Refactored: Technical deep-dive) +│ ├── cel-routing-cookbook.adoc (Existing) +│ ├── mcp-aggregation-guide.adoc (Existing) +│ ├── observability-logs.adoc (Existing) +│ ├── observability-metrics.adoc (Existing) +│ ├── migration-guide.adoc (Existing) +│ └── quickstart-enhanced.adoc (Existing or remove if redundant) +``` + +--- + +## Detailed Content Recommendations + +### 1. Create New index.adoc (HIGH PRIORITY) + +**Current State:** Minimal landing page with just a description +**Proposed Change:** Transform into a persona-based router + +**Content Structure:** +```asciidoc += AI Gateway +:description: Unified access layer for LLM providers and AI tools +:page-layout: index + +The Redpanda AI Gateway provides centralized routing, policy enforcement, cost management, and observability for all your AI traffic. + +== Choose Your Path + +[.persona-card] +=== I'm an Administrator +You manage AI Gateway infrastructure, configure providers, set policies, and monitor organizational usage. + +* xref:ai-gateway/admin/admin-overview.adoc[Admin Overview] +* xref:ai-gateway/admin/setup-guide.adoc[Setup Guide] +* xref:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] + +[.persona-card] +=== I'm a Builder +You're building AI agents or applications and need to connect to available gateways. + +* xref:ai-gateway/builders/builder-overview.adoc[Builder Overview] +* xref:ai-gateway/builders/discover-gateways.adoc[Discover Available Gateways] +* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] + +== Learn More + +* xref:ai-gateway/what-is-ai-gateway.adoc[What is an AI Gateway?] +* xref:ai-gateway/reference/ai-gateway-overview.adoc[Technical Architecture] +``` + +**Persona Tagging:** Both + +--- + +### 2. Create what-is-ai-gateway.adoc (HIGH PRIORITY) + +**Purpose:** Standalone conceptual page answering "What is an AI gateway?" +**Source:** Extract from ai-gateway-overview.adoc (lines 15-147) + +**Content to Include:** +- The problem AI Gateway solves +- Core capabilities (unified access, routing, MCP aggregation, observability) +- Common gateway patterns +- High-level architecture diagram + +**Remove from Overview:** Keep technical details in overview, move conceptual understanding here + +**Persona Tagging:** Both (Admin and Builder) + +--- + +### 3. Create discover-gateways.adoc (HIGH PRIORITY - NEW) + +**Purpose:** Help Builders find which gateways they have access to +**This is CRITICAL and completely missing from current content** + +**Content Structure:** +```asciidoc += Discover Available Gateways +:description: Find which AI Gateways you can access and their configurations +:page-personas: app_developer + +As a builder, you need to know which gateways are available to you before integrating your agent. + +== List your accessible gateways + +=== Using the Console + +1. Navigate to AI Gateway → My Gateways +2. View all gateways you have access to: + * Gateway Name + * Gateway ID (for `rp-aigw-id` header) + * Endpoint URL + * Available Models + * MCP Tools (if configured) + +=== Using the API + +[source,bash] +---- +curl https://{CLUSTER}.cloud.redpanda.com/api/ai-gateway/v1/gateways \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" +---- + +== Understanding gateway information + +Each gateway shows: + +* **Gateway ID**: Use this in the `rp-aigw-id` header +* **Endpoint URL**: Base URL for API requests +* **Available Models**: Which models you can access (e.g., `openai/gpt-4o`, `anthropic/claude-sonnet-3.5`) +* **Rate Limits**: Your request limits +* **MCP Tools**: Available MCP servers and tools (if enabled) + +== Check gateway availability + +Before integrating, test gateway access: + +[source,bash] +---- +curl https://{GATEWAY_ENDPOINT}/v1/models \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" +---- + +Expected response: List of available models + +== Next steps + +* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] +* xref:ai-gateway/builders/available-models.adoc[View Available Models] +``` + +**Persona Tagging:** Builder (app_developer) + +--- + +### 4. Refactor ai-gateway.adoc (quickstart) + +**Current Problem:** Mixes Admin setup (Steps 1-3) with Builder usage (Steps 4-5, integrations) + +**Proposed Split:** + +#### Create admin/setup-guide.adoc (Admin path) +- Step 1: Enable providers +- Step 2: Enable models +- Step 3: Create gateways +- Step 4: Configure LLM routing (policies, pools, rate limits) +- Step 5: Configure MCP tools + +#### Create builders/connect-your-agent.adoc (Builder path) +- Prerequisites: Gateway ID and endpoint (from discovery) +- Step 1: Get your gateway credentials +- Step 2: Configure your client SDK +- Step 3: Make your first request +- Step 4: Handle responses +- Step 5: Validate integration + +**Content to Move:** +- Lines 17-89 (Admin steps) → admin/setup-guide.adoc +- Lines 160-337 (Integration examples) → builders/connect-your-agent.adoc +- Lines 106-118 (Observability) → Link to observability pages + +--- + +### 5. Create admin/networking-configuration.adoc (MEDIUM PRIORITY) + +**Purpose:** Dedicated page for networking setup +**Content:** Currently mentioned but not detailed + +**Content Structure:** +```asciidoc += Networking Configuration +:description: Configure networking for AI Gateway including endpoints, private networking, and connectivity +:page-personas: platform_admin + +Configure network access and connectivity for your AI Gateway. + +== Gateway endpoints + +When you create a gateway, you receive: + +* Public endpoint: `https://gw.ai.panda.com` +* Private endpoint (if enabled): `https://gw-internal.ai.panda.com` + +== Public vs private endpoints + +**Public endpoints:** +- Accessible from internet +- Use for external agents, testing +- Standard TLS encryption + +**Private endpoints:** +- Accessible only within your VPC/network +- Use for production workloads +- Enhanced security + +== Configure private networking + +[PLACEHOLDER: Add private networking setup steps] + +== Connectivity requirements + +Outbound connections required: +- To LLM provider APIs (OpenAI, Anthropic, etc.) +- To configured MCP servers (if using MCP aggregation) + +Inbound connections: +- From your agents/applications to gateway endpoint + +== Firewall and security groups + +[PLACEHOLDER: Add security group configuration] + +== Next steps + +* xref:ai-gateway/admin/configure-policies.adoc[Configure Access Policies] +``` + +**Persona Tagging:** Admin (platform_admin) + +--- + +### 6. Create admin/access-controls.adoc (MEDIUM PRIORITY) + +**Purpose:** How Admins control who can access which gateways + +**Content:** +- Gateway-level access control +- API key management +- RBAC configuration (if available) +- Audit logging + +**Persona Tagging:** Admin (platform_admin) + +--- + +### 7. Update Existing Files + +#### ai-gateway-overview.adoc +**Changes:** +- Remove conceptual "What is" content (move to what-is-ai-gateway.adoc) +- Focus on technical architecture deep-dive +- Keep: Architecture details, request lifecycle, advanced patterns +- Update persona tag to: `platform_admin, app_developer` (both, but technical) + +#### cel-routing-cookbook.adoc +**Changes:** +- Add note at top: "This is an advanced reference for Admins configuring routing policies" +- Update persona tag to: `platform_admin` (currently has both) +- No content changes needed + +#### mcp-aggregation-guide.adoc +**Changes:** +- Add section for Builders: "Using MCP tools as a Builder" +- Currently too Admin-focused +- Add discovery section: How Builders see available MCP tools +- Keep persona tag: `app_developer` but clarify sections + +#### observability-logs.adoc +**Changes:** +- Add intro section distinguishing Admin vs Builder use cases: + - Admins: Monitor all traffic, all gateways, org-wide + - Builders: Monitor their own agent's requests +- Update UI paths to reflect persona-based views +- Persona tag is currently correct: `platform_admin, app_developer` + +#### observability-metrics.adoc +**Changes:** +- Similar to logs: Distinguish Admin (org-wide) vs Builder (my usage) views +- Add section: "View your agent's usage" (Builder perspective) +- Persona tag currently: `platform_admin` - should add `app_developer` + +--- + +## Navigation (nav.adoc) Changes + +**Current Structure:** +``` +* AI Gateway +** Overview +** Quickstart +** CEL Routing +** MCP Aggregation +** Observability +** Integrations +``` + +**Proposed Structure:** +``` +* xref:ai-agents:ai-gateway/index.adoc[AI Gateway] +** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[What is AI Gateway?] +** For Admins +*** xref:ai-agents:ai-gateway/admin/admin-overview.adoc[Admin Overview] +*** xref:ai-agents:ai-gateway/admin/setup-guide.adoc[Setup Guide] +*** xref:ai-agents:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] +*** xref:ai-agents:ai-gateway/admin/networking-configuration.adoc[Networking Configuration] +*** xref:ai-agents:ai-gateway/admin/configure-policies.adoc[Configure Policies] +*** xref:ai-agents:ai-gateway/admin/access-controls.adoc[Access Controls] +*** xref:ai-agents:ai-gateway/admin/observability-admin.adoc[Monitor Usage] +*** xref:ai-agents:ai-gateway/admin/integrations/index.adoc[Integrations (Admin)] +** For Builders +*** xref:ai-agents:ai-gateway/builders/builder-overview.adoc[Builder Overview] +*** xref:ai-agents:ai-gateway/builders/discover-gateways.adoc[Discover Gateways] +*** xref:ai-agents:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] +*** xref:ai-agents:ai-gateway/builders/available-models.adoc[Available Models] +*** xref:ai-agents:ai-gateway/builders/use-mcp-tools.adoc[Use MCP Tools] +*** xref:ai-agents:ai-gateway/builders/monitor-your-usage.adoc[Monitor Your Usage] +*** xref:ai-agents:ai-gateway/builders/integrations/index.adoc[Integrations (Builder)] +** Reference +*** xref:ai-agents:ai-gateway/reference/ai-gateway-overview.adoc[Architecture Deep Dive] +*** xref:ai-agents:ai-gateway/reference/cel-routing-cookbook.adoc[CEL Routing Cookbook] +*** xref:ai-agents:ai-gateway/reference/mcp-aggregation-guide.adoc[MCP Aggregation Guide] +*** xref:ai-agents:ai-gateway/reference/observability-logs.adoc[Request Logs] +*** xref:ai-agents:ai-gateway/reference/observability-metrics.adoc[Metrics and Analytics] +``` + +--- + +## Implementation Priority + +### Phase 1: Critical Path (Do First) +1. **Create index.adoc** - Persona router (HIGH) +2. **Create discover-gateways.adoc** - Critical Builder need (HIGH) +3. **Create what-is-ai-gateway.adoc** - Entry point (HIGH) +4. **Split quickstart** into admin/setup-guide.adoc and builders/connect-your-agent.adoc (HIGH) + +### Phase 2: Complete User Journeys +1. Create admin/manage-gateways.adoc (MEDIUM) +2. Create builders/available-models.adoc (MEDIUM) +3. Create admin/networking-configuration.adoc (MEDIUM) +4. Create admin/access-controls.adoc (MEDIUM) +5. Update observability pages with persona distinctions (MEDIUM) + +### Phase 3: Polish and Optimize +1. Refactor ai-gateway-overview.adoc (MEDIUM) +2. Update mcp-aggregation-guide.adoc with Builder sections (LOW) +3. Create admin/builder overview pages (LOW) +4. Reorganize integrations folders (LOW) +5. Update all cross-references (LOW) + +--- + +## Mapping to User Journey + +### Admin Journey → Content +| Journey Step | Content | +|--------------|---------| +| What is an AI gateway? | what-is-ai-gateway.adoc | +| How do I create, list, and manage gateways? | admin/setup-guide.adoc, admin/manage-gateways.adoc | +| Networking configuration & Gateway creation | admin/networking-configuration.adoc | +| Configure which models are accessible | admin/setup-guide.adoc (enable models section) | +| Configure access and routing policies | admin/configure-policies.adoc, cel-routing-cookbook.adoc | +| Track usage and configure budgeting | admin/setup-guide.adoc (budgets), observability-metrics.adoc | + +### Builder Journey → Content +| Journey Step | Content | +|--------------|---------| +| What is an AI gateway? | what-is-ai-gateway.adoc | +| Discover which AI gateways my agents have access to | **builders/discover-gateways.adoc (NEW)** | +| How do I integrate my agent? | builders/connect-your-agent.adoc | +| What models/tools are available? | builders/available-models.adoc, builders/use-mcp-tools.adoc | +| Test my integration | builders/connect-your-agent.adoc (validation section) | +| Track my usage | builders/monitor-your-usage.adoc → observability-logs.adoc | + +--- + +## Key Principles + +1. **Persona First:** Content should clearly identify which persona it serves +2. **Progressive Disclosure:** Start simple, link to advanced topics +3. **Minimize Duplication:** Use xrefs to avoid maintaining same content twice +4. **Clear Entry Points:** Index page must route users effectively +5. **Discovery is Critical:** Builders MUST be able to find available gateways + +--- + +## Success Metrics + +After implementation, evaluate: +- Can a Builder discover available gateways in <2 minutes? +- Can an Admin complete setup in <15 minutes? +- Do users report clearer distinction between Admin vs Builder tasks? +- Reduced support tickets about "I can't find which gateway to use" + +--- + +## Open Questions + +1. **API for Gateway Discovery:** Does the API support listing accessible gateways per user? +2. **RBAC Model:** How granular is access control (workspace, gateway, model level)? +3. **Private Networking:** What's the detailed setup for private endpoints? +4. **Budgets and Limits:** Can Builders see their own usage/limits, or only Admins? +5. **Integration Folders:** Should we physically split integration files into admin/ and builders/ subdirectories? + +--- + +## Next Steps + +1. **Review this plan** with product and docs team +2. **Validate API capabilities** for gateway discovery +3. **Create Phase 1 content** (index, discover-gateways, what-is, split quickstart) +4. **Test with users** from each persona +5. **Iterate based on feedback** + +--- + +## Appendix: File Operations Summary + +### New Files to Create +- `ai-gateway/index.adoc` (replace existing minimal one) +- `ai-gateway/what-is-ai-gateway.adoc` +- `ai-gateway/admin/admin-overview.adoc` +- `ai-gateway/admin/setup-guide.adoc` +- `ai-gateway/admin/manage-gateways.adoc` +- `ai-gateway/admin/networking-configuration.adoc` +- `ai-gateway/admin/configure-policies.adoc` +- `ai-gateway/admin/access-controls.adoc` +- `ai-gateway/builders/builder-overview.adoc` +- `ai-gateway/builders/discover-gateways.adoc` ⭐ CRITICAL +- `ai-gateway/builders/connect-your-agent.adoc` +- `ai-gateway/builders/available-models.adoc` +- `ai-gateway/builders/use-mcp-tools.adoc` +- `ai-gateway/builders/monitor-your-usage.adoc` + +### Files to Move +- `ai-gateway/ai-gateway-overview.adoc` → `ai-gateway/reference/ai-gateway-overview.adoc` +- `ai-gateway/cel-routing-cookbook.adoc` → `ai-gateway/reference/cel-routing-cookbook.adoc` +- `ai-gateway/mcp-aggregation-guide.adoc` → `ai-gateway/reference/mcp-aggregation-guide.adoc` +- `ai-gateway/observability-*.adoc` → `ai-gateway/reference/observability-*.adoc` + +### Files to Refactor +- `ai-gateway/ai-gateway.adoc` (quickstart) - split content between admin and builder paths +- `ai-gateway/ai-gateway-overview.adoc` - extract conceptual content to what-is page +- `ai-gateway/observability-logs.adoc` - add persona-specific sections +- `ai-gateway/observability-metrics.adoc` - add builder usage section + +### Files to Keep As-Is (Minimal Changes) +- `ai-gateway/integrations/*-admin.adoc` +- `ai-gateway/integrations/*-user.adoc` +- `ai-gateway/migration-guide.adoc` +- `ai-gateway/quickstart-enhanced.adoc` (review if still needed) From 344ee083f1970275c0999c7984cf3d6d77fb6e09 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Thu, 22 Jan 2026 17:12:40 +0000 Subject: [PATCH 24/97] Remove sessions and tasks topics from agent documentation Sessions and tasks topics are being soft-hidden with __ prefix as they are internal implementation details. Updated documentation to focus on user-facing monitoring features (transcripts and inspector). Changes: - Remove "Agent data topics" section from concepts.adoc with schemas - Remove "Consume agent data topics" section from monitor-agents.adoc - Update troubleshooting.adoc to reference transcripts instead of topics - Update learning objective to "Track token usage and performance metrics" - Fix xref anchor links to include descriptive text - Fix shipping carrier name to comply with Google style guide Co-Authored-By: Claude Sonnet 4.5 --- .../processors/get_shipping_info.yaml | 2 +- .../mcp-tools/processors/order_workflow.yaml | 6 +- .../ai-agents/pages/agents/a2a-concepts.adoc | 38 ++- .../pages/agents/architecture-patterns.adoc | 65 +---- modules/ai-agents/pages/agents/concepts.adoc | 119 +-------- .../ai-agents/pages/agents/create-agent.adoc | 35 ++- .../pages/agents/integration-overview.adoc | 6 +- .../pages/agents/monitor-agents.adoc | 195 ++++++-------- modules/ai-agents/pages/agents/overview.adoc | 10 +- .../agents/pipeline-integration-patterns.adoc | 8 +- .../pages/agents/prompt-best-practices.adoc | 6 +- .../ai-agents/pages/agents/quickstart.adoc | 10 +- .../pages/agents/troubleshooting.adoc | 32 ++- .../tutorials/customer-support-agent.adoc | 8 +- .../ai-agents/pages/mcp/local/overview.adoc | 4 +- modules/ai-agents/pages/mcp/overview.adoc | 6 +- .../pages/mcp/remote/best-practices.adoc | 6 +- .../ai-agents/pages/mcp/remote/concepts.adoc | 8 +- .../pages/mcp/remote/create-tool.adoc | 10 +- .../pages/mcp/remote/manage-servers.adoc | 6 +- .../pages/mcp/remote/monitor-mcp-servers.adoc | 81 +++--- .../ai-agents/pages/mcp/remote/overview.adoc | 6 +- .../pages/mcp/remote/quickstart.adoc | 14 +- .../pages/mcp/remote/tool-patterns.adoc | 8 +- .../pages/mcp/remote/troubleshooting.adoc | 6 +- .../pages/observability/concepts.adoc | 237 ++++++++++++++++-- .../partials/transcripts-ui-guide.adoc | 89 +++++++ 27 files changed, 558 insertions(+), 463 deletions(-) create mode 100644 modules/ai-agents/partials/transcripts-ui-guide.adoc diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_shipping_info.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_shipping_info.yaml index 40ca21dfa..b0a15b497 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/get_shipping_info.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/get_shipping_info.yaml @@ -7,7 +7,7 @@ processors: { "order_id": $order_id, "tracking_number": "FX1234567890", - "carrier": "FedEx", + "carrier": "Example Shipping", "status": "in_transit", "estimated_delivery": "2025-01-17", "last_location": "San Francisco Distribution Center", diff --git a/modules/ai-agents/examples/mcp-tools/processors/order_workflow.yaml b/modules/ai-agents/examples/mcp-tools/processors/order_workflow.yaml index ad0dc29ee..aebaac897 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/order_workflow.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/order_workflow.yaml @@ -39,7 +39,7 @@ processors: root = this.merge({ "processing_tier": "premium", "processing_time_estimate": "2-4 hours", - "assigned_rep": "premium-team@company.com", + "assigned_rep": "premium-team@example.com", "priority_score": 95 }) @@ -51,7 +51,7 @@ processors: root = this.merge({ "processing_tier": "vip", "processing_time_estimate": "1-2 hours", - "assigned_rep": "vip-team@company.com", + "assigned_rep": "vip-team@example.com", "priority_score": 90, "perks": ["expedited_shipping", "white_glove_service"] }) @@ -63,7 +63,7 @@ processors: root = this.merge({ "processing_tier": "standard", "processing_time_estimate": "24-48 hours", - "assigned_rep": "support@company.com", + "assigned_rep": "support@example.com", "priority_score": 50 }) diff --git a/modules/ai-agents/pages/agents/a2a-concepts.adoc b/modules/ai-agents/pages/agents/a2a-concepts.adoc index 1b58a4bee..0e08e2219 100644 --- a/modules/ai-agents/pages/agents/a2a-concepts.adoc +++ b/modules/ai-agents/pages/agents/a2a-concepts.adoc @@ -23,10 +23,10 @@ Agents that implement A2A expose their capabilities through a standardized agent The protocol provides: -* **Standardized discovery:** Agent cards describe capabilities in a machine-readable format. -* **Platform independence:** Any system can call any A2A-compliant agent. -* **Version negotiation:** Protocol versions ensure compatibility between agents. -* **Communication mode flexibility:** Supports synchronous request/response and streaming. +* Standardized discovery: Agent cards describe capabilities in a machine-readable format. +* Platform independence: Any system can call any A2A-compliant agent. +* Version negotiation: Protocol versions ensure compatibility between agents. +* Communication mode flexibility: Supports synchronous request/response and streaming. For the complete specification, see link:https://a2a.ag/spec[a2a.ag/spec^]. @@ -49,10 +49,10 @@ The `.well-known` path follows internet standards for service discovery, making An agent card describes: -* **Agent capabilities:** Skills and tools the agent provides. -* **Input/output formats:** Expected request and response structures. -* **Protocol version:** A2A specification version the agent supports. -* **Communication modes:** Whether the agent supports synchronous calls, streaming, or both. +* Agent capabilities: Skills and tools the agent provides. +* Input/output formats: Expected request and response structures. +* Protocol version: A2A specification version the agent supports. +* Communication modes: Whether the agent supports synchronous calls, streaming, or both. Example agent card structure: @@ -89,19 +89,13 @@ For integration pattern guidance, see xref:ai-agents:agents/integration-overview === Internal pipeline-to-agent integration -Redpanda Connect pipelines use the xref:develop:connect/components/processors/a2a_message.adoc[`a2a_message`] processor to invoke agents for each event in a stream: - ----- -Redpanda Connect Pipeline sends events to a2a_message (A2A) which invokes Redpanda Cloud Agent ----- - -Use cases: +Redpanda Connect pipelines use the xref:develop:connect/components/processors/a2a_message.adoc[`a2a_message`] processor to invoke agents for each event in a stream. This enables real-time interaction between streaming data and AI agents, enabling use cases like: * Real-time fraud detection on every transaction. * Streaming data enrichment with AI-generated fields. * Event-driven agent invocation for automated processing. -The xref:develop:connect/components/processors/a2a_message.adoc[`a2a_message`] processor uses A2A protocol internally to discover and call agents. For pipeline patterns, see xref:ai-agents:agents/pipeline-integration-patterns.adoc[]. +The `a2a_message` processor uses the A2A protocol internally to discover and call agents. For pipeline patterns, see xref:ai-agents:agents/pipeline-integration-patterns.adoc[]. == How agents discover each other @@ -133,9 +127,9 @@ External callers use these credentials to obtain access tokens: This flow ensures: -* **Credentials stay secure:** Applications never send them directly to agents, only access tokens. -* **Exposure is limited:** Tokens expire, reducing the window for compromised credentials. -* **Integration is standard:** Applications can use existing OAuth2 libraries. +* Credentials stay secure: Applications never send them directly to agents, only access tokens. +* Exposure is limited: Tokens expire, reducing the window for compromised credentials. +* Integration is standard: Applications can use existing OAuth2 libraries. === External integration @@ -153,6 +147,6 @@ The A2A protocol uses semantic versioning (major.minor.patch). Agents declare th == Next steps -* xref:ai-agents:agents/integration-overview.adoc[]: Choose the right integration pattern for your use case -* xref:ai-agents:agents/create-agent.adoc[]: Create an agent that exposes an A2A agent card -* link:https://a2a.ag/spec[A2A Protocol Specification^]: Complete technical reference +* xref:ai-agents:agents/integration-overview.adoc[] +* xref:ai-agents:agents/create-agent.adoc[] +* link:https://a2a.ag/spec[A2A Protocol Specification^] diff --git a/modules/ai-agents/pages/agents/architecture-patterns.adoc b/modules/ai-agents/pages/agents/architecture-patterns.adoc index 69097e040..1e619a2c4 100644 --- a/modules/ai-agents/pages/agents/architecture-patterns.adoc +++ b/modules/ai-agents/pages/agents/architecture-patterns.adoc @@ -18,14 +18,10 @@ After reading this page, you will be able to: Agent architecture determines how you manage complexity as your system grows. The right pattern depends on your domain complexity, organizational structure, and how you expect requirements to evolve. -=== When simple agents become unmaintainable - -Single agents work well initially but break down as complexity grows. +Starting with a simple architecture is tempting, but can lead to unmaintainable systems as complexity increases. Planning for growth with clear boundaries prevents technical debt and costly refactoring later. Warning signs include system prompts exceeding 2000 words, too many tools for the LLM to select correctly, multiple teams modifying the same agent, and changes in one domain breaking others. These symptoms indicate you need architectural boundaries, not just better prompts. -=== Domain complexity drives architecture - Match agent architecture to domain structure: [cols="2,3,3"] @@ -45,7 +41,6 @@ Match agent architecture to domain structure: | Organizational boundaries require system boundaries |=== -=== Trade-offs Every architecture pattern involves trade-offs. @@ -131,33 +126,6 @@ External A2A lets different teams own and deploy their agents independently, wit External A2A adds network latency on every cross-agent call, and authentication complexity multiplies with each agent requiring credential management. Removing capabilities or changing contracts requires coordination across consuming systems, and debugging requires tracing requests across organizational boundaries. -=== Choosing between patterns - -[cols="1,2,2"] -|=== -| Use Case | Internal Subagents | External A2A - -| Domain separation within one team -| Recommended -| Unnecessary complexity - -| Cross-organization workflows -| Not possible -| Required - -| Shared infrastructure acceptable -| Simpler -| Use external if independence needed - -| Different deployment requirements -| Limited (same cluster) -| Full flexibility - -| Real-time performance critical -| Lower latency -| Higher latency -|=== - For implementation details on external A2A integration, see xref:ai-agents:agents/integration-overview.adoc[]. == Common anti-patterns @@ -252,31 +220,10 @@ Use retry logic for transient failures like network timeouts. Report permanent f Provide clear error messages to users. Log errors for debugging. -== Summary - -Choose architecture patterns based on domain complexity and organizational needs: - -[cols="1,2,2"] -|=== -| Pattern | Use When | Trade-off - -| Single agent -| Narrow domain, single organization -| Limited scalability - -| Internal subagents -| Complex domains, shared infrastructure -| Higher operational complexity - -| External A2A -| Multi-organization, independent systems -| Network latency, coordination overhead -|=== - == Next steps -* xref:ai-agents:agents/integration-overview.adoc[]: Choose between agents invoking tools and pipelines calling agents -* xref:ai-agents:agents/a2a-concepts.adoc[]: Learn how the A2A protocol enables agent communication -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Explore tool design patterns -* xref:ai-agents:agents/overview.adoc[]: Review agent components -* xref:ai-agents:mcp/remote/best-practices.adoc[]: Learn MCP tool best practices +* xref:ai-agents:agents/integration-overview.adoc[] +* xref:ai-agents:agents/a2a-concepts.adoc[] +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] +* xref:ai-agents:agents/overview.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] diff --git a/modules/ai-agents/pages/agents/concepts.adoc b/modules/ai-agents/pages/agents/concepts.adoc index 5de95ceee..43cc6e2c6 100644 --- a/modules/ai-agents/pages/agents/concepts.adoc +++ b/modules/ai-agents/pages/agents/concepts.adoc @@ -133,8 +133,6 @@ The agent's context includes the system prompt (always present), user messages, Agents persist conversation context within a session. When you use the *Inspector* tab in the Redpanda Cloud Console, it automatically maintains session state across multiple requests. For programmatic access, applications must pass a session ID to maintain conversation continuity across requests. -All conversation data is stored in the agent's sessions topic. For details on accessing conversation history, see <>. - === Context window limits LLM context windows limit how much history fits. Small models support 8K-32K tokens, medium models support 32K-128K tokens, and large models support 128K-1M+ tokens. @@ -145,120 +143,13 @@ Design workflows to complete within context limits. Avoid unbounded tool chainin === State across conversations -Redpanda Cloud automatically persists conversation history and task execution data to agent-specific topics: - -* *Sessions topic* (`redpanda.aiagent..sessions`): Stores all conversation messages and history -* *Tasks topic* (`redpanda.aiagent..tasks`): Stores task execution records with status and artifacts - -The *Inspector* tab automatically manages sessions for you. For programmatic integration, pass a session ID in API requests to maintain conversation continuity. - -You can consume from these topics to: - -* Review past conversations and agent responses -* Analyze conversation patterns and common requests -* Debug agent behavior by examining full conversation history -* Build conversation analytics and monitoring dashboards -* Implement custom conversation management in your application - -For complete details on topic schemas and usage, see <>. +Redpanda Cloud automatically persists conversation history. The *Inspector* tab automatically manages sessions for you. For programmatic integration, pass a session ID in API requests to maintain conversation continuity. If you need additional state management beyond conversation history, create tools that read/write to custom state stores, or pass relevant context in each request. -[[agent-data-topics]] -== Agent data topics - -Each agent automatically creates two topics for storing session and task data: - -* `redpanda.aiagent..sessions`: Conversation history and session state -* `redpanda.aiagent..tasks`: Task execution records with status and artifacts - -Two schemas are also registered: `redpanda-session-value` and `redpanda-a2a-task-value`. - -Redpanda uses these topics internally to persist conversation history and reload context when sessions resume. This allows agents to maintain continuity across interactions. - -These topics and schemas are managed automatically by Redpanda. If you delete either a topic or schema, they are recreated automatically. However, deleting a topic permanently deletes all stored data (including conversation history), and the topic comes back empty. Do not produce your own data to these topics. They are reserved for agent data. - -For guidance on consuming these topics, analyzing conversation history, and monitoring agent performance, see xref:ai-agents:agents/monitor-agents.adoc[]. - -=== Sessions topic - -The sessions topic stores conversation data, including all messages exchanged between users and the agent: - -[source,json] ----- -{ - "id": "agent-chat-abc123-WqyUmxv0fuACoSE69MCx4", - "messages": [ - { - "role": "MESSAGE_ROLE_USER", - "content": [ - { - "kind": "PART_KIND_TEXT", - "text": "What's the weather in Seattle?" - } - ] - }, - { - "role": "MESSAGE_ROLE_ASSISTANT", - "content": [ - { - "kind": "PART_KIND_TEXT", - "text": "The current weather in Seattle is 52°F with light rain." - } - ] - } - ], - "metadata": {} -} ----- - -Use session data for debugging conversation flow, auditing agent interactions, and building conversation analytics. - -=== Tasks topic - -The tasks topic stores execution records for each agent task, including status, artifacts, message history, and token usage: - -[source,json] ----- -{ - "id": "019bc2d5-13e0-785b-9ff4-a218708e52bc", - "contextId": "agent-chat-abc123-oGoqyBCeH5RHTsit8qpRi", - "status": { - "state": "TASK_STATE_COMPLETED", - "timestamp": "2026-01-15T18:05:03.993157355Z" - }, - "artifacts": [ - { - "artifactId": "019bc2d5-186a-76c7-97d1-feb13c75a2d4", - "parts": [ - { - "text": "The current weather in Seattle is 52°F with light rain." - } - ] - } - ], - "history": [...], - "metadata": { - "usage": { - "input_tokens": 64, - "output_tokens": 9, - "total_tokens": 130 - } - } -} ----- - -Task states include: - -* `TASK_STATE_COMPLETED`: Task finished successfully -* `TASK_STATE_FAILED`: Task encountered an error -* `TASK_STATE_RUNNING`: Task is in progress - -Use task data for monitoring task completion rates, tracking token usage and costs, debugging failed tasks, and building operational dashboards. - == Next steps -* xref:ai-agents:agents/architecture-patterns.adoc[]: Apply these concepts to design patterns -* xref:ai-agents:agents/quickstart.adoc[]: See execution concepts in action -* xref:ai-agents:agents/prompt-best-practices.adoc[]: Write prompts that guide agent reasoning -* xref:ai-agents:mcp/remote/best-practices.adoc[]: Design tools that work well with agent execution +* xref:ai-agents:agents/architecture-patterns.adoc[] +* xref:ai-agents:agents/quickstart.adoc[] +* xref:ai-agents:agents/prompt-best-practices.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] diff --git a/modules/ai-agents/pages/agents/create-agent.adoc b/modules/ai-agents/pages/agents/create-agent.adoc index a59a011e8..ae4e60750 100644 --- a/modules/ai-agents/pages/agents/create-agent.adoc +++ b/modules/ai-agents/pages/agents/create-agent.adoc @@ -166,24 +166,19 @@ For multi-agent design patterns, see xref:ai-agents:agents/architecture-patterns Max iterations determine how many reasoning loops the agent can perform before stopping. Each iteration consumes tokens and adds latency. For detailed cost calculations and the cost/capability/latency trade-off, see xref:ai-agents:agents/concepts.adoc[]. -. In the *Execution Settings* section, configure *Max Iterations* (range: 10-100, default: 30). -+ +In the *Execution Settings* section, configure *Max Iterations* (range: 10-100, default: 30). + Choose based on task complexity: -+ + * **Simple queries** (10-20): Single tool call, direct answers, minimal reasoning * **Balanced workflows** (20-40): Multiple tool calls, data aggregation, moderate analysis * **Complex analysis** (40-100): Exploratory queries, extensive tool chaining, deep reasoning -+ + Start with 30 for most use cases. === Review and create -. Review all settings: -+ -* Model and API key -* System prompt -* Selected tools -* Max iterations +. Review all settings. . Configure the service account name (optional): + @@ -209,17 +204,19 @@ For programmatic access or external agent integration, see xref:ai-agents:agents == Test your agent -. In the agent details view, click *Test*. -. Enter a test query. +. In the agent details view, click the *Inspector* tab. +. Enter a test prompt. . Verify the agent: + * Selects appropriate tools * Follows system prompt constraints * Returns expected output format -. Iterate on system prompt or tool selection as needed. +. Iterate on the system prompt or tool selection as needed. + +== Example configurations -== Examples +Here are example configurations for different agent types: === Simple query agent @@ -245,8 +242,8 @@ For programmatic access or external agent integration, see xref:ai-agents:agents == Next steps -* xref:ai-agents:agents/integration-overview.adoc[]: Connect your agent to external applications and pipelines -* xref:ai-agents:agents/prompt-best-practices.adoc[]: Refine your system prompts -* xref:ai-agents:mcp/remote/create-tool.adoc[]: Create additional tools -* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems -* xref:ai-agents:agents/troubleshooting.adoc[]: Diagnose and fix common agent issues +* xref:ai-agents:agents/integration-overview.adoc[] +* xref:ai-agents:agents/prompt-best-practices.adoc[] +* xref:ai-agents:mcp/remote/create-tool.adoc[] +* xref:ai-agents:agents/architecture-patterns.adoc[] +* xref:ai-agents:agents/troubleshooting.adoc[] diff --git a/modules/ai-agents/pages/agents/integration-overview.adoc b/modules/ai-agents/pages/agents/integration-overview.adoc index 3efc49408..ecba9080b 100644 --- a/modules/ai-agents/pages/agents/integration-overview.adoc +++ b/modules/ai-agents/pages/agents/integration-overview.adoc @@ -123,6 +123,6 @@ Access tokens grant full access to the agent. Anyone with a valid token can send == Next steps -* xref:ai-agents:agents/a2a-concepts.adoc[]: Learn about the A2A protocol that powers agent communication -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Explore MCP tool patterns and examples -* xref:ai-agents:agents/pipeline-integration-patterns.adoc[]: Build pipelines that call agents +* xref:ai-agents:agents/a2a-concepts.adoc[] +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] +* xref:ai-agents:agents/pipeline-integration-patterns.adoc[] diff --git a/modules/ai-agents/pages/agents/monitor-agents.adoc b/modules/ai-agents/pages/agents/monitor-agents.adoc index d4ea356c7..def5c6d63 100644 --- a/modules/ai-agents/pages/agents/monitor-agents.adoc +++ b/modules/ai-agents/pages/agents/monitor-agents.adoc @@ -1,12 +1,12 @@ = Monitor Agent Activity -:description: Monitor agent execution, analyze conversation history, track token usage, and debug issues using Inspector, Transcripts, and agent data topics. +:description: Monitor agent execution, analyze conversation history, track token usage, and debug issues using inspector, transcripts, and agent data topics. :page-topic-type: how-to :personas: agent_developer, platform_admin -:learning-objective-1: Test agents interactively using the Inspector tab -:learning-objective-2: Consume session and task topics for analysis -:learning-objective-3: Debug agent behavior using Transcripts +:learning-objective-1: pass:q[Verify agent behavior using the *Inspector* tab] +:learning-objective-2: Track token usage and performance metrics +:learning-objective-3: Debug agent execution using transcripts -Monitor your agents to track performance, analyze conversations, debug issues, and optimize costs. +Use monitoring to track agent performance, analyze conversation patterns, debug execution issues, and optimize token costs. After reading this page, you will be able to: @@ -20,157 +20,114 @@ For conceptual background on traces and observability, see xref:ai-agents:observ You must have a running agent. If you do not have one, see xref:ai-agents:agents/quickstart.adoc[]. -== Test agents interactively +== Debug agent execution with transcripts -The *Inspector* tab provides real-time conversation testing and debugging. Use it to test agent responses interactively, view full conversation history, see tool invocations and results, monitor token usage per request, and test error scenarios. +The transcripts view shows execution traces with detailed timing, errors, and performance metrics. Use this view to debug issues, verify agent behavior, and monitor performance in real-time. -=== Access the Inspector +:context: agent +include::ai-agents:partial$transcripts-ui-guide.adoc[] -. Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud Console. -. Click your agent name. -. Open the *Inspector* tab. -. Enter test queries and review responses. -. Check the conversation panel to see tool calls. -. Start a new session to test fresh conversations. - -=== Testing best practices - -Test your agents systematically with these scenarios: - -* **Boundary cases**: Test requests at the edge of agent capabilities to verify scope enforcement. -* **Error handling**: Request unavailable data to verify graceful degradation. -* **Iteration count**: Monitor how many iterations complex requests require. -* **Ambiguous input**: Send vague queries to verify clarification behavior. -* **Token usage**: Track tokens per request to estimate costs. +=== Check agent health -== View execution traces +Use the transcripts view to verify your agent is healthy: -The *Transcripts* view shows agent execution traces with detailed timing and error information. Transcripts capture agent startup and initialization, request and response flow, tool invocations and results, error messages and failures, token usage per request, and OpenTelemetry trace data. +Healthy agent indicators: -=== Access Transcripts - -. Navigate to *Agentic AI* > *AI Agents*. -. Click your agent name. -. Click *Transcripts* in the left navigation. -. Select a transcript to view execution details. +* Timeline shows consistent green bars (successful executions) +* Duration stays within expected range (check summary panel) +* Token usage is stable (not growing unexpectedly) +* LLM calls match expected patterns (1-3 calls for simple queries) +* No error bars in timeline -Use Transcripts when diagnosing deployment issues (agent won't start), debugging tool execution errors, investigating unexpected agent behavior, analyzing conversation flow, or verifying tool selection logic. +Warning signs: -== Consume agent data topics +* Red bars in timeline: Errors or failures, click to investigate +* Increasing duration: May indicate context window growth or slow tool calls +* High token usage: Check if conversation history is too long +* Many LLM calls: Agent may be stuck in loops or making unnecessary iterations +* Missing transcripts: Agent may be stopped or encountering deployment issues -Agents emit structured data to two Redpanda topics for monitoring and analysis. +Common patterns to investigate: -=== Sessions topic +* All recent transcripts show errors: Check agent status, MCP server connectivity, or system prompt +* Duration increasing over session: Context window filling up, consider clearing conversation history +* Spiky timeline (alternating success/error): Intermittent tool failures or external API issues +* High token usage with few LLM calls: Large tool results or verbose system prompts -The sessions topic (`redpanda.aiagent..sessions`) contains all conversation messages. This topic lets you review past conversations, analyze conversation patterns, debug multi-turn interactions, and understand context management. +=== Debug with transcripts -=== Tasks topic +Use transcripts to diagnose specific issues: -The tasks topic (`redpanda.aiagent..tasks`) contains task execution records with status and artifacts. Use this topic to monitor task completion rates, track token usage and costs, debug failed tasks, and analyze agent performance. +Agent not responding -=== Access agent topics +. Check the timeline for recent transcripts. If none appear, the agent may be stopped. +. Verify agent status in the main *AI Agents* view. +. Look for error transcripts with deployment or initialization failures. -[tabs] -===== -Cloud Console:: -+ --- -. Navigate to *Topics* in the Redpanda Cloud Console. -. Find `redpanda.aiagent..sessions` or `redpanda.aiagent..tasks`. -. Click *Messages* to view recent data. -. Use filters to search for specific sessions or task states. --- +Tool execution errors -rpk:: -+ --- -Consume recent sessions: +. Select the failed transcript (red bar in timeline). +. Expand the trace hierarchy to find the tool invocation span. +. Check the span details for error messages. +. Cross-reference with MCP server status. -[,bash] ----- -rpk topic consume redpanda.aiagent..sessions --offset end -n 10 ----- +Slow performance -Consume recent tasks: +. Compare duration across multiple transcripts in the summary panel. +. Look for specific spans with long durations (wide bars in trace list). +. Check if LLM calls are taking longer than expected. +. Verify tool execution time by examining nested spans. -[,bash] ----- -rpk topic consume redpanda.aiagent..tasks --offset end -n 10 ----- --- +Unexpected behavior -Data Plane API:: -+ --- -Use the link:/api/doc/cloud-dataplane/[Data Plane API] to programmatically consume agent data and integrate with your monitoring pipeline. --- -===== +. Select the transcript for the problematic request. +. Expand the full trace hierarchy to see all operations. +. Look for missing tool calls (agent didn't invoke expected tools). +. Check LLM call count: excessive calls may indicate loops. -For schema details, see xref:ai-agents:agents/concepts.adoc#agent-data-topics[Agent data topics]. +=== Track token usage and costs -== Analyze conversation history +View token consumption in the Summary panel when you select a transcript: -Use conversation history to identify behavior patterns and diagnose issues. When analyzing conversations, watch for agents calling the same tool repeatedly (indicates loop detection is needed), large gaps between messages (suggests tool timeout or slow execution), agent responses without tool calls (indicates a tool selection issue), fabricated information (suggests a missing "never make up data" constraint), and truncated early messages (indicates the context window was exceeded). +* Input tokens: Tokens sent to the LLM (system prompt + conversation history + tool results) +* Output tokens: Tokens generated by the LLM (agent responses) +* Total tokens: Sum of input and output -=== Analysis workflow +Calculate cost per request: -. Use Inspector to reproduce the issue. -. Review full conversation including tool invocations. -. Identify where agent behavior diverged from expected. -. Check system prompt for missing guidance. -. Verify tool responses are formatted correctly. - -== Track token usage - -Monitor token consumption to optimize costs and set appropriate iteration limits. Token usage appears in three places: the *Inspector* tab shows tokens per request during interactive testing, the *Tasks* topic contains `usage` metadata with input/output/total tokens, and *Transcripts* displays token counts for each execution. - -=== Calculate costs - -Use token data from the tasks topic to estimate costs: - -[,bash] ---- -Cost per request = (total_tokens × model_price_per_token) +Cost = (input_tokens × input_price) + (output_tokens × output_price) ---- -Track costs over time by consuming the tasks topic, extracting `metadata.usage.total_tokens` from each task, multiplying by your model's token price, and aggregating by time period. +Example: GPT-4o with 4,302 input tokens and 1,340 output tokens at $0.0000025 per input token and $0.00001 per output token costs $0.024 per request. For cost optimization strategies, see xref:ai-agents:agents/concepts.adoc#cost-calculation[Cost calculation]. -== Monitor performance - -Track agent performance metrics to identify bottlenecks. The table below shows key metrics you can extract from agent data topics: - -[cols="2,3", options="header"] -|=== -| Metric | Description - -| Task completion rate -| Percentage of tasks with `TASK_STATE_COMPLETED` +== Test agent behavior with the inspector -| Average iterations -| Mean iterations per task from conversation history +The *Inspector* tab provides real-time conversation testing. Use it to test agent responses interactively and verify behavior before deploying changes. -| Token efficiency -| Tokens consumed per successful task completion +=== Access the inspector -| Tool invocation frequency -| Which tools are called most often +. Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud Console. +. Click your agent name. +. Open the *Inspector* tab. +. Enter test queries and review responses. +. Check the conversation panel to see tool calls. +. Start a new session to test fresh conversations or click *Clear context* to reset history. -| Error rate -| Percentage of tasks with `TASK_STATE_FAILED` -|=== +=== Testing best practices -=== Performance analysis +Test your agents systematically with these scenarios: -. Consume the tasks topic over a time window. -. Calculate completion rate: `completed_tasks / total_tasks`. -. Identify high iteration tasks for prompt optimization. -. Track token usage trends over time. -. Correlate errors with specific tool invocations. +* Boundary cases: Test requests at the edge of agent capabilities to verify scope enforcement. +* Error handling: Request unavailable data to verify graceful degradation. +* Iteration count: Monitor how many iterations complex requests require. +* Ambiguous input: Send vague queries to verify clarification behavior. +* Token usage: Track tokens per request to estimate costs. == Next steps -* xref:ai-agents:observability/concepts.adoc[]: Understand trace structure and OpenTelemetry spans -* xref:ai-agents:agents/troubleshooting.adoc[]: Diagnose and fix common agent issues -* xref:ai-agents:agents/concepts.adoc[]: Learn about agent execution and cost calculation +* xref:ai-agents:observability/concepts.adoc[] +* xref:ai-agents:agents/troubleshooting.adoc[] +* xref:ai-agents:agents/concepts.adoc[] diff --git a/modules/ai-agents/pages/agents/overview.adoc b/modules/ai-agents/pages/agents/overview.adoc index 1e2120e1b..1b8af540b 100644 --- a/modules/ai-agents/pages/agents/overview.adoc +++ b/modules/ai-agents/pages/agents/overview.adoc @@ -61,8 +61,8 @@ Process every event with AI reasoning at scale. Invoke agents automatically from == Next steps -* xref:ai-agents:agents/quickstart.adoc[]: Get your first agent running -* xref:ai-agents:agents/concepts.adoc[]: Understand agent execution, context, and state management -* xref:ai-agents:agents/architecture-patterns.adoc[]: Explore agent design patterns -* xref:ai-agents:agents/integration-overview.adoc[]: Choose between agents invoking tools and pipelines calling agents -* xref:ai-agents:agents/create-agent.adoc[]: Create an agent through the Cloud Console +* xref:ai-agents:agents/quickstart.adoc[] +* xref:ai-agents:agents/concepts.adoc[] +* xref:ai-agents:agents/architecture-patterns.adoc[] +* xref:ai-agents:agents/integration-overview.adoc[] +* xref:ai-agents:agents/create-agent.adoc[] diff --git a/modules/ai-agents/pages/agents/pipeline-integration-patterns.adoc b/modules/ai-agents/pages/agents/pipeline-integration-patterns.adoc index e47fa1856..6395d344c 100644 --- a/modules/ai-agents/pages/agents/pipeline-integration-patterns.adoc +++ b/modules/ai-agents/pages/agents/pipeline-integration-patterns.adoc @@ -137,7 +137,7 @@ This pipeline: == Next steps -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Explore MCP tool patterns for agent-initiated integration -* xref:ai-agents:agents/integration-overview.adoc[]: Review all integration patterns -* xref:ai-agents:agents/a2a-concepts.adoc[]: Learn how the `a2a_message` processor uses the A2A protocol -* xref:develop:connect/components/processors/about.adoc[]: Learn about Redpanda Connect processors +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] +* xref:ai-agents:agents/integration-overview.adoc[] +* xref:ai-agents:agents/a2a-concepts.adoc[] +* xref:develop:connect/components/processors/about.adoc[] diff --git a/modules/ai-agents/pages/agents/prompt-best-practices.adoc b/modules/ai-agents/pages/agents/prompt-best-practices.adoc index 7da48eb56..f0f83ce57 100644 --- a/modules/ai-agents/pages/agents/prompt-best-practices.adoc +++ b/modules/ai-agents/pages/agents/prompt-best-practices.adoc @@ -419,6 +419,6 @@ Decision criteria enable reliable tool selection based on request context. == Next steps -* xref:ai-agents:agents/quickstart.adoc[]: Create an agent with your system prompt -* xref:ai-agents:agents/overview.adoc[]: Review agent components -* xref:ai-agents:mcp/remote/best-practices.adoc[]: Learn MCP tool design patterns +* xref:ai-agents:agents/quickstart.adoc[] +* xref:ai-agents:agents/overview.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] diff --git a/modules/ai-agents/pages/agents/quickstart.adoc b/modules/ai-agents/pages/agents/quickstart.adoc index 526c1368f..8e25a0c02 100644 --- a/modules/ai-agents/pages/agents/quickstart.adoc +++ b/modules/ai-agents/pages/agents/quickstart.adoc @@ -187,8 +187,8 @@ Common quickstart issues: You've created an agent that orchestrates MCP tools through natural language. Explore more: -* xref:ai-agents:agents/overview.adoc[]: Learn the four essential agent components -* xref:ai-agents:agents/create-agent.adoc[]: Deep dive into agent configuration options -* xref:ai-agents:agents/prompt-best-practices.adoc[]: Write better system prompts -* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Find more tool patterns to add to your agent +* xref:ai-agents:agents/overview.adoc[] +* xref:ai-agents:agents/create-agent.adoc[] +* xref:ai-agents:agents/prompt-best-practices.adoc[] +* xref:ai-agents:agents/architecture-patterns.adoc[] +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] diff --git a/modules/ai-agents/pages/agents/troubleshooting.adoc b/modules/ai-agents/pages/agents/troubleshooting.adoc index f51dbd91d..9f55ad422 100644 --- a/modules/ai-agents/pages/agents/troubleshooting.adoc +++ b/modules/ai-agents/pages/agents/troubleshooting.adoc @@ -177,6 +177,28 @@ Critical rules: * Test with requests that require unavailable data * Monitor transcripts and session topic for fabricated responses +=== Analyzing conversation patterns + +**Symptoms:** Agent behavior is inconsistent or produces unexpected results. + +**Solution:** + +Review conversation history in transcripts to identify problematic patterns: + +* Agents calling the same tool repeatedly: Indicates loop detection is needed +* Large gaps between messages: Suggests tool timeout or slow execution +* Agent responses without tool calls: Indicates a tool selection issue +* Fabricated information: Suggests a missing "never make up data" constraint +* Truncated early messages: Indicates the context window was exceeded + +**Analysis workflow:** + +. Use inspector to reproduce the issue. +. Review full conversation including tool invocations. +. Identify where agent behavior diverged from expected. +. Check system prompt for missing guidance. +. Verify tool responses are formatted correctly. + == Performance issues Diagnose and fix issues related to agent speed and resource consumption. @@ -225,7 +247,7 @@ Diagnose and fix issues related to agent speed and resource consumption. **Solution:** -. Review token usage in the tasks topic. +. Review token usage in transcripts. . Lower max iterations for this agent. . Optimize tool responses to return less data: + @@ -429,7 +451,7 @@ For comprehensive guidance on monitoring agent activity, analyzing conversation == Next steps -* xref:ai-agents:agents/prompt-best-practices.adoc[]: Write better system prompts to prevent common issues -* xref:ai-agents:agents/concepts.adoc[]: Understand agent execution and context management -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Design reliable tools -* xref:ai-agents:agents/architecture-patterns.adoc[]: Apply scalable design patterns +* xref:ai-agents:agents/prompt-best-practices.adoc[] +* xref:ai-agents:agents/concepts.adoc[] +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] +* xref:ai-agents:agents/architecture-patterns.adoc[] diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index 352ee08d1..4c239340c 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -354,7 +354,7 @@ Use these documented test IDs when testing the agent. If you replace the mock to == Next steps -* xref:ai-agents:mcp/remote/tool-patterns.adoc#call-external-apis[Call external APIs]: Replace mock tools with HTTP API calls -* xref:ai-agents:agents/prompt-best-practices.adoc[]: Learn more system prompt patterns -* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems -* xref:ai-agents:agents/troubleshooting.adoc[]: Debug agent behavior issues +* xref:ai-agents:mcp/remote/tool-patterns.adoc#call-external-apis[Call external APIs] +* xref:ai-agents:agents/prompt-best-practices.adoc[] +* xref:ai-agents:agents/architecture-patterns.adoc[] +* xref:ai-agents:agents/troubleshooting.adoc[] diff --git a/modules/ai-agents/pages/mcp/local/overview.adoc b/modules/ai-agents/pages/mcp/local/overview.adoc index a27e3df25..6b2643a34 100644 --- a/modules/ai-agents/pages/mcp/local/overview.adoc +++ b/modules/ai-agents/pages/mcp/local/overview.adoc @@ -66,7 +66,7 @@ MCP servers authenticate to Redpanda Cloud using your personal or service accoun == Next steps -* xref:ai-agents:mcp/local/quickstart.adoc[Redpanda Cloud Management MCP Server quickstart] -* xref:ai-agents:mcp/local/configuration.adoc[Configure the Redpanda Cloud Management MCP Server] +* xref:ai-agents:mcp/local/quickstart.adoc[] +* xref:ai-agents:mcp/local/configuration.adoc[] TIP: The Redpanda documentation site has a read-only MCP server that provides access to Redpanda docs and examples. This server has no access to your Redpanda Cloud account or clusters. See xref:home:ROOT:mcp-setup.adoc[]. diff --git a/modules/ai-agents/pages/mcp/overview.adoc b/modules/ai-agents/pages/mcp/overview.adoc index 4e4282b7a..964be2dc5 100644 --- a/modules/ai-agents/pages/mcp/overview.adoc +++ b/modules/ai-agents/pages/mcp/overview.adoc @@ -85,9 +85,9 @@ You can use both options together. For example, use the Redpanda Cloud Managemen == Get started -* xref:ai-agents:mcp/local/quickstart.adoc[]: Connect Claude to your Redpanda Cloud account -* xref:ai-agents:mcp/remote/quickstart.adoc[]: Build and deploy custom MCP tools +* xref:ai-agents:mcp/local/quickstart.adoc[] +* xref:ai-agents:mcp/remote/quickstart.adoc[] == Suggested reading -* xref:home:ROOT:mcp-setup.adoc[]: Access Redpanda documentation through AI agents (read-only, no Cloud access required) +* xref:home:ROOT:mcp-setup.adoc[] diff --git a/modules/ai-agents/pages/mcp/remote/best-practices.adoc b/modules/ai-agents/pages/mcp/remote/best-practices.adoc index bf1eed69c..81738ab01 100644 --- a/modules/ai-agents/pages/mcp/remote/best-practices.adoc +++ b/modules/ai-agents/pages/mcp/remote/best-practices.adoc @@ -37,6 +37,6 @@ include::redpanda-connect:ai-agents:example$best-practices/mcp-metadata/search-c == Next steps -* xref:ai-agents:mcp/remote/create-tool.adoc#secrets[Use secrets]: Store credentials securely in the Secrets Store -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Find reusable patterns including validation, error handling, and response formatting -* xref:ai-agents:mcp/remote/troubleshooting.adoc[]: Diagnose common issues +* xref:ai-agents:mcp/remote/create-tool.adoc#secrets[Use secrets for credentials] +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] +* xref:ai-agents:mcp/remote/troubleshooting.adoc[] diff --git a/modules/ai-agents/pages/mcp/remote/concepts.adoc b/modules/ai-agents/pages/mcp/remote/concepts.adoc index 57a366539..db7f22ada 100644 --- a/modules/ai-agents/pages/mcp/remote/concepts.adoc +++ b/modules/ai-agents/pages/mcp/remote/concepts.adoc @@ -39,7 +39,7 @@ To monitor MCP server activity, consume traces, and debug failures, see xref:ai- == Next steps -* xref:ai-agents:mcp/remote/create-tool.adoc[]: Create MCP tools for your agents -* xref:ai-agents:mcp/remote/best-practices.adoc[]: Apply naming and design guidelines -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Find reusable patterns -* xref:ai-agents:mcp/remote/troubleshooting.adoc[]: Diagnose common issues +* xref:ai-agents:mcp/remote/create-tool.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] +* xref:ai-agents:mcp/remote/troubleshooting.adoc[] diff --git a/modules/ai-agents/pages/mcp/remote/create-tool.adoc b/modules/ai-agents/pages/mcp/remote/create-tool.adoc index ef40bdde9..d856b8607 100644 --- a/modules/ai-agents/pages/mcp/remote/create-tool.adoc +++ b/modules/ai-agents/pages/mcp/remote/create-tool.adoc @@ -253,8 +253,8 @@ include::ai-agents:example$mcp-tools/processors/get_weather_complete.yaml[tag=co == Next steps -* xref:ai-agents:agents/quickstart.adoc[]: Build an AI agent that uses your tools -* xref:ai-agents:mcp/remote/best-practices.adoc[]: Apply naming and design guidelines -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Find patterns for databases, APIs, and Redpanda -* xref:ai-agents:mcp/remote/troubleshooting.adoc[]: Diagnose common issues -* xref:develop:connect/components/about.adoc[]: Browse all available components +* xref:ai-agents:agents/quickstart.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] +* xref:ai-agents:mcp/remote/troubleshooting.adoc[] +* xref:develop:connect/components/about.adoc[] diff --git a/modules/ai-agents/pages/mcp/remote/manage-servers.adoc b/modules/ai-agents/pages/mcp/remote/manage-servers.adoc index b1c993254..40fe836f7 100644 --- a/modules/ai-agents/pages/mcp/remote/manage-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/manage-servers.adoc @@ -162,6 +162,6 @@ Deletion is immediate and permanent. Make sure you have backed up any important == Next steps -* xref:ai-agents:mcp/remote/scale-resources.adoc[Scale MCP server resources] to optimize performance and costs. -* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[Monitor MCP server activity] using OpenTelemetry traces. -* xref:ai-agents:mcp/remote/best-practices.adoc[Learn best practices] for building robust tools. +* xref:ai-agents:mcp/remote/scale-resources.adoc[] +* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] diff --git a/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc index b0ea50962..20813596a 100644 --- a/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc @@ -6,7 +6,7 @@ :learning-objective-2: Track tool invocations and measure performance :learning-objective-3: Debug tool failures using trace data -Monitor MCP server activity using OpenTelemetry traces emitted to the `redpanda.otel_traces` topic. +Monitor MCP server activity using OpenTelemetry traces emitted to the `redpanda.otel_traces` glossterm:topic[]. After reading this page, you will be able to: @@ -20,9 +20,16 @@ For conceptual background on traces, spans, and the trace data structure, see xr You must have an existing MCP server. If you do not have one, see xref:ai-agents:mcp/remote/quickstart.adoc[]. -== Consume traces +== View transcripts in the Cloud Console -MCP servers emit OpenTelemetry traces to the `redpanda.otel_traces` topic. You can consume these traces using any Kafka-compatible client or the Redpanda Cloud Console. +:context: mcp +include::ai-agents:partial$transcripts-ui-guide.adoc[] + +== Analyze traces programmatically + +MCP servers emit OpenTelemetry traces to the `redpanda.otel_traces` topic. Consume these traces to build custom monitoring, track tool usage, and analyze performance. + +=== Consume traces [tabs] ===== @@ -51,61 +58,47 @@ Filter for specific MCP server activity by examining the span attributes. Data Plane API:: + -- -Use the link:/api/doc/cloud-dataplane/[Data Plane API] to programmatically consume traces and integrate with your monitoring pipeline. +Use the link:/api/doc/cloud-dataplane/[Data Plane API^] to programmatically consume traces and integrate with your monitoring pipeline. -- ===== -== Track tool invocations - -Monitor which tools are being called and how often: - -. Consume traces from `redpanda.otel_traces`. -. Filter spans where `instrumentationScope.name` is `rpcn-mcp`. -. Examine the `name` field to see which tools are being invoked. -. Calculate frequency by counting spans per tool name over time windows. - -Example: To find all invocations of a specific tool, filter for spans where `name` matches your tool name (for example, `weather`, `http_processor`). - -== Measure performance - -Analyze tool execution times: - -. Find spans with `instrumentationScope.name` set to `rpcn-mcp`. -. Calculate duration: `(endTimeUnixNano - startTimeUnixNano) / 1000000` (milliseconds). -. Track percentiles (p50, p95, p99) to identify performance issues. -. Set alerts for durations exceeding acceptable thresholds. +=== Track tool invocations -Example: A span with `startTimeUnixNano: "1765198415253280028"` and `endTimeUnixNano: "1765198424660663434"` has a duration of 9407ms. +Monitor which tools are being called and how often by filtering spans where `instrumentationScope.name` is `rpcn-mcp`. The `name` field shows which tool was invoked. -== Debug failures +Example: Find all invocations of a specific tool: -Investigate errors and failures: +[,bash] +---- +rpk topic consume redpanda.otel_traces --offset start \ + | jq 'select(.instrumentationScope.name == "rpcn-mcp" and .name == "weather")' +---- -. Filter spans where `status.code` is `2` (error). -. Examine `status.message` for error details. -. Check the `events` array for error events with timestamps. -. Use `traceId` to correlate related spans and understand the full error context. -. Follow `parentSpanId` relationships to trace the error back to the originating tool. +=== Measure performance -Example: A span with `status.code: 2` and `status.message: "connection timeout"` indicates the operation failed due to a timeout. +Calculate tool execution time using span timestamps: -== Correlate distributed operations +[,bash] +---- +Duration (ms) = (endTimeUnixNano - startTimeUnixNano) / 1000000 +---- -Link MCP server activity to downstream effects: +Track percentiles (p50, p95, p99) to identify performance issues and set alerts for durations exceeding acceptable thresholds. -. Extract `traceId` from tool invocation spans. -. Search for the same `traceId` in other application logs or traces. -. Follow `parentSpanId` relationships to build complete operation timelines. -. Identify bottlenecks across your entire system. +=== Debug failures -== Integrate with observability platforms +Filter for error spans where `status.code` is `2`: -The `redpanda.otel_traces` topic stores trace data in OpenTelemetry format. Redpanda does not support direct export to platforms like Grafana Cloud and Datadog due to format compatibility limitations. Redpanda produces one span per topic message, whereas these platforms expect traces in batch format. +[,bash] +---- +rpk topic consume redpanda.otel_traces --offset start \ + | jq 'select(.status.code == 2)' +---- -You can consume traces directly from the `redpanda.otel_traces` topic using any Kafka-compatible consumer for custom analysis and processing. +Check `status.message` for error details and the `events` array for error events with timestamps. Use `traceId` to correlate related spans across the distributed system. == Next steps -* xref:ai-agents:observability/concepts.adoc[]: Learn how traces and spans work -* xref:ai-agents:mcp/remote/troubleshooting.adoc[]: Diagnose and fix common issues -* xref:ai-agents:mcp/remote/manage-servers.adoc[]: Manage MCP server lifecycle +* xref:ai-agents:observability/concepts.adoc[] +* xref:ai-agents:mcp/remote/troubleshooting.adoc[] +* xref:ai-agents:mcp/remote/manage-servers.adoc[] diff --git a/modules/ai-agents/pages/mcp/remote/overview.adoc b/modules/ai-agents/pages/mcp/remote/overview.adoc index 8ab577444..7e5aac62d 100644 --- a/modules/ai-agents/pages/mcp/remote/overview.adoc +++ b/modules/ai-agents/pages/mcp/remote/overview.adoc @@ -65,8 +65,8 @@ include::redpanda-connect:ai-agents:partial$mcp/overview/specification-support.a == Next steps * xref:ai-agents:mcp/remote/quickstart.adoc[] -* xref:ai-agents:agents/overview.adoc[]: Learn about Redpanda AI Agents that use MCP tools -* xref:ai-agents:mcp/remote/concepts.adoc[]: Learn about execution and component types -* xref:ai-agents:mcp/remote/create-tool.adoc[]: Create custom tools step by step +* xref:ai-agents:agents/overview.adoc[] +* xref:ai-agents:mcp/remote/concepts.adoc[] +* xref:ai-agents:mcp/remote/create-tool.adoc[] * link:https://modelcontextprotocol.io/[Model Context Protocol documentation^] diff --git a/modules/ai-agents/pages/mcp/remote/quickstart.adoc b/modules/ai-agents/pages/mcp/remote/quickstart.adoc index a295005d3..92ee94aeb 100644 --- a/modules/ai-agents/pages/mcp/remote/quickstart.adoc +++ b/modules/ai-agents/pages/mcp/remote/quickstart.adoc @@ -389,10 +389,10 @@ For detailed solutions, see xref:ai-agents:mcp/remote/troubleshooting.adoc[]. You've deployed an MCP server and connected Claude Code to your Redpanda cluster. Here's where to go next: -* xref:ai-agents:agents/quickstart.adoc[]: Build and deploy an AI agent that uses your MCP tools -* xref:ai-agents:mcp/remote/concepts.adoc[]: Understand how MCP tools differ from pipelines -* xref:ai-agents:mcp/remote/create-tool.adoc[]: Build production-quality tools with validation -* xref:ai-agents:mcp/remote/best-practices.adoc[]: Apply naming and design guidelines -* xref:ai-agents:mcp/remote/tool-patterns.adoc[]: Find reusable patterns -* xref:ai-agents:mcp/remote/troubleshooting.adoc[]: Diagnose common issues -* xref:ai-agents:mcp/remote/admin-guide.adoc[]: Scale resources, monitor activity, and administer your MCP servers +* xref:ai-agents:agents/quickstart.adoc[] +* xref:ai-agents:mcp/remote/concepts.adoc[] +* xref:ai-agents:mcp/remote/create-tool.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] +* xref:ai-agents:mcp/remote/tool-patterns.adoc[] +* xref:ai-agents:mcp/remote/troubleshooting.adoc[] +* xref:ai-agents:mcp/remote/admin-guide.adoc[] diff --git a/modules/ai-agents/pages/mcp/remote/tool-patterns.adoc b/modules/ai-agents/pages/mcp/remote/tool-patterns.adoc index edf1e0d43..2e9c658f0 100644 --- a/modules/ai-agents/pages/mcp/remote/tool-patterns.adoc +++ b/modules/ai-agents/pages/mcp/remote/tool-patterns.adoc @@ -252,7 +252,7 @@ include::redpanda-connect:ai-agents:partial$mcp/tool-patterns/production-workflo == Next steps -* xref:ai-agents:agents/integration-overview.adoc[]: Choose between agents invoking MCP tools and pipelines calling agents -* xref:ai-agents:mcp/remote/create-tool.adoc[]: Step-by-step tool creation guide -* xref:ai-agents:mcp/remote/best-practices.adoc[]: Apply naming and design guidelines -* xref:ai-agents:mcp/remote/troubleshooting.adoc[]: Diagnose and fix common issues +* xref:ai-agents:agents/integration-overview.adoc[] +* xref:ai-agents:mcp/remote/create-tool.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] +* xref:ai-agents:mcp/remote/troubleshooting.adoc[] diff --git a/modules/ai-agents/pages/mcp/remote/troubleshooting.adoc b/modules/ai-agents/pages/mcp/remote/troubleshooting.adoc index 51bdb1301..2dd384758 100644 --- a/modules/ai-agents/pages/mcp/remote/troubleshooting.adoc +++ b/modules/ai-agents/pages/mcp/remote/troubleshooting.adoc @@ -39,8 +39,8 @@ include::redpanda-connect:ai-agents:partial$mcp/troubleshooting/debugging-techni If you're still experiencing issues: -* xref:ai-agents:mcp/remote/create-tool.adoc[]: Review YAML structure rules and metadata fields -* xref:ai-agents:mcp/remote/best-practices.adoc[]: Review naming and metadata design -* xref:ai-agents:mcp/remote/concepts.adoc[]: Review component type selection +* xref:ai-agents:mcp/remote/create-tool.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] +* xref:ai-agents:mcp/remote/concepts.adoc[] For protocol-level troubleshooting, see the link:https://modelcontextprotocol.io/[MCP documentation^]. diff --git a/modules/ai-agents/pages/observability/concepts.adoc b/modules/ai-agents/pages/observability/concepts.adoc index 9b09130ba..99b0fa6a3 100644 --- a/modules/ai-agents/pages/observability/concepts.adoc +++ b/modules/ai-agents/pages/observability/concepts.adoc @@ -16,7 +16,7 @@ After reading this page, you will be able to: == What are transcripts -Every agent and MCP server automatically emits OpenTelemetry traces to a topic called `redpanda.otel_traces`. These traces provide detailed observability into operations, creating complete transcripts. +Every agent and MCP server automatically emits OpenTelemetry traces to a glossterm:topic[] called `redpanda.otel_traces`. These traces provide detailed observability into operations, creating complete transcripts. Transcripts capture: @@ -37,20 +37,210 @@ OpenTelemetry traces provide a complete picture of how a request flows through y * A _span_ represents a single unit of work within that trace (such as a data processing operation or an external API call). * A trace contains one or more spans organized hierarchically, showing how operations relate to each other. -[[opentelemetry-traces-topic]] -== How Redpanda stores traces +== Agent trace hierarchy -The `redpanda.otel_traces` topic stores OpenTelemetry spans in JSON format, following the https://opentelemetry.io/docs/specs/otel/protocol/[OpenTelemetry Protocol (OTLP)^] specification. A Protobuf schema named `redpanda.otel_traces-value` is also automatically registered with the topic, enabling clients to deserialize trace data correctly. +Agent executions create a hierarchy of spans that reflect how agents process requests. Understanding this hierarchy helps you interpret agent behavior and identify where issues occur. -The `redpanda.otel_traces` topic and its schema are managed automatically by Redpanda. If you delete either the topic or the schema, they are recreated automatically. However, deleting the topic permanently deletes all trace data, and the topic comes back empty. Do not produce your own data to this topic. It is reserved for OpenTelemetry traces. +=== Agent span types -=== Topic configuration and lifecycle +Agent traces contain these span types: -The `redpanda.otel_traces` topic has a predefined retention policy. Configuration changes to this topic are not supported. If you modify settings, Redpanda reverts them to the default values. +[cols="2,3,3", options="header"] +|=== +| Span Type | Description | Use To -The topic persists in your cluster even after all agents and MCP servers are deleted, allowing you to retain historical trace data for analysis. +| `ai-agent` +| Top-level span representing the entire agent invocation from start to finish. Includes all processing time, from receiving the request through executing the reasoning loop, calling tools, and returning the final response. +| Measure total request duration and identify slow agent invocations. + +| `agent` +| Internal agent processing that represents reasoning and decision-making. Shows time spent in the LLM reasoning loop, including context processing, tool selection, and response generation. Multiple `agent` spans may appear when the agent iterates through its reasoning loop. +| Track reasoning time and identify iteration patterns. + +| `invoke_agent` +| Agent and sub-agent invocation ( in multi-agent architectures). Represents one agent calling another via the A2A protocol. +| Trace calls between root agents and sub-agents, measure cross-agent latency, and identify which sub-agent was invoked. + +| `openai`, `anthropic`, or other LLM providers +| LLM provider API call showing calls to the language model. The span name matches the provider, and attributes typically include the model name (like `gpt-4o` or `claude-sonnet-4`). +| Identify which model was called, measure LLM response time, and debug LLM API errors. + +| `rpcn-mcp` +| MCP tool invocation representing calls to Remote MCP servers. Shows tool execution time, including network latency and tool processing. Child spans with `instrumentationScope.name` set to `redpanda-connect` represent internal Redpanda Connect processing. +| Measure tool execution time and identify slow MCP tool calls. +|=== + +=== Typical agent execution flow + +A simple agent request creates this hierarchy: + +---- +ai-agent (6.65 seconds) +├── agent (6.41 seconds) +│ ├── invoke_agent: customer-support-agent (6.39 seconds) +│ │ └── openai: chat gpt-4o (6.2 seconds) +---- + +This shows: + +1. Total agent invocation: 6.65 seconds +2. Agent reasoning: 6.41 seconds +3. Sub-agent call: 6.39 seconds (most of the time) +4. LLM API call: 6.2 seconds (the actual bottleneck) + +Examine span durations to identify where time is spent and optimize accordingly. + +== MCP server trace hierarchy + +MCP server executions create a different hierarchy that reflects tool invocations and internal processing. Understanding this hierarchy helps you debug tool execution and identify performance bottlenecks. + +=== MCP server span types + +MCP server traces contain these span types: + +[cols="2,3,3", options="header"] +|=== +| Span Type | Description | Use To + +| `mcp-{server-id}` +| Top-level span representing the entire MCP server invocation. The server ID uniquely identifies the MCP server instance. This span encompasses all tool execution from request receipt to response completion. +| Measure total MCP server response time and identify slow tool invocations. + +| `service` +| Internal service processing span that appears at multiple levels in the hierarchy. Represents Redpanda Connect service operations including routing, processing, and component execution. +| Track internal processing overhead and identify where time is spent in the service layer. + +| Tool name (e.g., `get_order_status`, `get_customer_history`) +| The specific MCP tool being invoked. This span name matches the tool name defined in the MCP server configuration. +| Identify which tool was called and measure tool-specific execution time. + +| `processors` +| Processor pipeline execution span showing the collection of processors that process the tool's data. Appears as a child of the tool invocation span. +| Measure total processor pipeline execution time. + +| Processor name (e.g., `mapping`, `http`, `branch`) +| Individual processor execution span representing a single Redpanda Connect processor. The span name matches the processor type. +| Identify slow processors and debug processing logic. +|=== + +=== Typical MCP server execution flow + +An MCP tool invocation creates this hierarchy: + +---- +mcp-d5mnvn251oos73 (4.00 seconds) +├── service > get_order_status (4.07 seconds) +│ └── service > processors (43 microseconds) +│ └── service > mapping (18 microseconds) +---- + +This shows: + +1. Total MCP server invocation: 4.00 seconds +2. Tool execution (get_order_status): 4.07 seconds +3. Processor pipeline: 43 microseconds +4. Mapping processor: 18 microseconds (data transformation) + +The majority of time (4+ seconds) is spent in tool execution, while internal processing (mapping) takes only microseconds. This indicates the tool itself (likely making external API calls or database queries) is the bottleneck, not Redpanda Connect's internal processing. + +== Trace layers and scope + +Traces contain multiple layers of instrumentation, from HTTP transport through application logic to external service calls. The `scope.name` field in each span identifies which layer of instrumentation created that span. + +=== Instrumentation layers + +A complete agent trace includes these layers: -Trace data may contain sensitive information from your tool inputs and outputs. Consider implementing appropriate glossterm:ACL[,access control lists (ACLs)] for the `redpanda.otel_traces` topic, and review the data in traces before sharing or exporting to external systems. +[cols="2,2,4", options="header"] +|=== +| Layer | Scope Name | Purpose + +| HTTP Server +| `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` +| HTTP transport layer receiving requests. Shows request/response sizes, status codes, client addresses, and network details. + +| AI SDK (Agent) +| `github.com/redpanda-data/ai-sdk-go/plugins/otel` +| Agent application logic. Shows agent invocations, LLM calls, tool executions, conversation IDs, token usage, and model details. Includes `gen_ai.*` semantic convention attributes. + +| HTTP Client +| `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` +| Outbound HTTP calls from agent to MCP servers. Shows target URLs, request methods, and response codes. + +| MCP Server +| `rpcn-mcp` +| MCP server tool execution. Shows tool name, input parameters, result size, and execution time. Appears as a separate `service.name` in resource attributes. + +| Redpanda Connect +| `redpanda-connect` +| Internal Redpanda Connect component execution within MCP tools. Shows pipeline and individual component spans. +|=== + +=== How layers connect + +Layers connect through parent-child relationships in a single trace: + +---- +ai-agent-http-server (HTTP Server layer) +└── invoke_agent customer-support-agent (AI SDK layer) + ├── chat gpt-5-nano (AI SDK layer, LLM call 1) + ├── execute_tool get_order_status (AI SDK layer) + │ └── HTTP POST (HTTP Client layer) + │ └── get_order_status (MCP Server layer, different service) + │ └── processors (Redpanda Connect layer) + └── chat gpt-5-nano (AI SDK layer, LLM call 2) +---- + +This shows: + +1. HTTP request arrives at agent +2. Agent invokes sub-agent +3. Agent makes first LLM call to decide what to do +4. Agent executes tool, making HTTP call to MCP server +5. MCP server processes tool through its pipeline +6. Agent makes second LLM call with tool results +7. Response returns through HTTP layer + +=== Cross-service traces + +When agents call MCP tools, the trace spans multiple services. Each service has a different `service.name` in the resource attributes: + +* Agent spans: `"service.name": "ai-agent"` +* MCP server spans: `"service.name": "mcp-{server-id}"` + +Both use the same `traceId`, allowing you to follow a request across service boundaries. + +=== Key attributes by layer + +Different layers expose different attributes: + +HTTP Server/Client layer: + +- `http.request.method`, `http.response.status_code` +- `server.address`, `url.path`, `url.full` +- `network.peer.address`, `network.peer.port` +- `http.request.body.size`, `http.response.body.size` + +AI SDK layer: + +- `gen_ai.operation.name`: Operation type (`invoke_agent`, `chat`, `execute_tool`) +- `gen_ai.conversation.id`: Links spans to the same conversation +- `gen_ai.agent.name`: Sub-agent name for multi-agent systems +- `gen_ai.provider.name`, `gen_ai.request.model`: LLM provider and model +- `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`: Token consumption +- `gen_ai.tool.name`, `gen_ai.tool.call.arguments`: Tool execution details +- `gen_ai.input.messages`, `gen_ai.output.messages`: Full LLM conversation context + +MCP Server layer: + +- Tool-specific attributes like `order_id`, `customer_id` +- `result_prefix`, `result_length`: Tool result metadata + +Redpanda Connect layer: + +- Component-specific attributes from your tool configuration + +Use `scope.name` to filter spans by layer when analyzing traces. == Understand the trace structure @@ -75,13 +265,13 @@ Each span captures a unit of work. Here's what a typical MCP tool invocation loo Key elements to understand: -* **`traceId`**: Links all spans belonging to the same request. Use this to follow a tool invocation through its entire lifecycle. -* **`name`**: The tool or operation name (`http_processor` in this example). This tells you which component was invoked. -* **`instrumentationScope.name`**: When this is `rpcn-mcp`, the span represents an MCP tool. When it's `redpanda-connect`, it's internal processing. -* **`attributes`**: Context about the operation, like input parameters or result metadata. -* **`status.code`**: `0` means success, `2` means error. +* `traceId`: Links all spans belonging to the same request. Use this to follow a tool invocation through its entire lifecycle. +* `name`: The tool or operation name (`http_processor` in this example). This tells you which component was invoked. +* `instrumentationScope.name`: When this is `rpcn-mcp`, the span represents an MCP tool. When it's `redpanda-connect`, it's internal processing. +* `attributes`: Context about the operation, like input parameters or result metadata. +* `status.code`: `0` means success, `2` means error. -== Parent-child relationships +=== Parent-child relationships Traces show how operations relate. A tool invocation (parent) may trigger internal operations (children): @@ -97,7 +287,7 @@ Traces show how operations relate. A tool invocation (parent) may trigger intern } ---- -The `parentSpanId` links this child span to the parent tool invocation. Both share the same `traceId`, so you can reconstruct the complete operation. +The `parentSpanId` links this child span to the parent tool invocation. Both share the same `traceId` so you can reconstruct the complete operation. == Error events in traces @@ -123,6 +313,21 @@ When something goes wrong, traces capture error details: The `events` array captures what happened and when. Use `timeUnixNano` to see exactly when the error occurred within the operation. +[[opentelemetry-traces-topic]] +== How Redpanda stores traces + +The `redpanda.otel_traces` topic stores OpenTelemetry spans in JSON format, following the https://opentelemetry.io/docs/specs/otel/protocol/[OpenTelemetry Protocol (OTLP)^] specification. A Protobuf schema named `redpanda.otel_traces-value` is also automatically registered with the topic, enabling clients to deserialize trace data correctly. + +The `redpanda.otel_traces` topic and its schema are managed automatically by Redpanda. If you delete either the topic or the schema, they are recreated automatically. However, deleting the topic permanently deletes all trace data, and the topic comes back empty. Do not produce your own data to this topic. It is reserved for OpenTelemetry traces. + +=== Topic configuration and lifecycle + +The `redpanda.otel_traces` topic has a predefined retention policy. Configuration changes to this topic are not supported. If you modify settings, Redpanda reverts them to the default values. + +The topic persists in your cluster even after all agents and MCP servers are deleted, allowing you to retain historical trace data for analysis. + +Trace data may contain sensitive information from your tool inputs and outputs. Consider implementing appropriate glossterm:ACL[access control lists (ACLs)] for the `redpanda.otel_traces` topic, and review the data in traces before sharing or exporting to external systems. + == Traces compared to audit logs OpenTelemetry traces are designed for observability and debugging, not audit logging or compliance. diff --git a/modules/ai-agents/partials/transcripts-ui-guide.adoc b/modules/ai-agents/partials/transcripts-ui-guide.adoc new file mode 100644 index 000000000..5d7d00604 --- /dev/null +++ b/modules/ai-agents/partials/transcripts-ui-guide.adoc @@ -0,0 +1,89 @@ +// ============================================================================= +// PARTIAL: transcripts-ui-guide.adoc +// ============================================================================= +// +// PURPOSE: +// Documents the Transcripts UI interface for both AI agents and MCP servers. +// Single-sources UI navigation and component descriptions that are identical +// across both contexts. +// +// INCLUDED BY: +// - cloud-docs: modules/ai-agents/pages/agents/monitor-agents.adoc +// - cloud-docs: modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc +// +// INCLUDE SYNTAX: +// :context: agent +// include::partial$transcripts-ui-guide.adoc[] +// +// :context: mcp +// include::partial$transcripts-ui-guide.adoc[] +// +// ATTRIBUTES USED: +// - context: Controls agent-specific vs MCP-specific content +// Valid values: "agent" | "mcp" +// +// DEPENDENCIES: +// - xref:ai-agents:observability/concepts.adoc#agent-trace-hierarchy[] +// - xref:ai-agents:observability/concepts.adoc#mcp-server-trace-hierarchy[] +// +// CONTENT TYPE: +// UI navigation and interface explanation (procedural context for how-to pages) +// +// ============================================================================= + +=== Navigate the transcripts view + +// Navigation is identical for both contexts +. In the left navigation panel, click *Transcripts*. +ifeval::["{context}" == "agent"] +. Select a recent transcript from your agent executions. +endif::[] +ifeval::["{context}" == "mcp"] +. Select a recent transcript from your MCP server tool invocations. +endif::[] + +The transcripts view displays: + +* *Timeline* (top): Visual history of recent executions with success/error indicators +* *Trace list* (middle): Hierarchical view of traces and spans +* *Summary panel* (right): Detailed metrics when you select a transcript + +// UI component descriptions +==== Timeline visualization + +The timeline at the top shows execution patterns over time: + +* Green bars: Successful executions +* Red bars: Failed executions with errors +* Gray bars: Incomplete traces or traces still loading +* Time range: Displays the last few hours by default + +Use the timeline to spot patterns like error clusters, performance degradation over time, or gaps indicating downtime. + +==== Trace hierarchy + +The trace list shows nested operations with visual duration bars indicating how long each operation took. Click the expand arrows (▶) to drill into nested spans and see the complete execution flow. + +// Link to appropriate concepts section based on context +ifeval::["{context}" == "agent"] +For details on span types, see xref:ai-agents:observability/concepts.adoc#agent-trace-hierarchy[Agent trace hierarchy]. +endif::[] +ifeval::["{context}" == "mcp"] +For details on span types, see xref:ai-agents:observability/concepts.adoc#mcp-server-trace-hierarchy[MCP server trace hierarchy]. +endif::[] + +==== Summary panel + +When you select a transcript, the right panel shows: + +* Duration: Total execution time for this request +* Total Spans: Number of operations in the trace +ifeval::["{context}" == "agent"] +* Token Usage: Input tokens, output tokens, and total (critical for cost tracking) +* LLM Calls: How many times the agent called the language model +* Service: The agent identifier +* Conversation ID: Links to session data topics +endif::[] +ifeval::["{context}" == "mcp"] +* Service: The MCP server identifier +endif::[] From 01d61b52cb16215445e9edff12070997e3244493 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Thu, 22 Jan 2026 12:14:32 -0700 Subject: [PATCH 25/97] update nav --- modules/ROOT/nav.adoc | 100 +++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 6b62b9119..0e2c153e4 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -23,52 +23,6 @@ *** xref:get-started:cluster-types/byoc/remote-read-replicas.adoc[] ** xref:get-started:cluster-types/create-dedicated-cloud-cluster.adoc[] -* xref:networking:index.adoc[Networking] -** xref:networking:cloud-security-network.adoc[] -** xref:networking:cidr-ranges.adoc[] -** xref:networking:byoc/index.adoc[BYOC] -*** xref:networking:byoc/aws/index.adoc[AWS] -**** xref:networking:byoc/aws/vpc-peering-aws.adoc[Add a Peering Connection] -**** xref:networking:configure-privatelink-in-cloud-ui.adoc[Configure PrivateLink in the Cloud UI] -**** xref:networking:aws-privatelink.adoc[Configure PrivateLink with the Cloud API] -**** xref:networking:byoc/aws/transit-gateway.adoc[Add a Transit Gateway] -*** xref:networking:byoc/azure/index.adoc[Azure] -**** xref:networking:azure-private-link-in-ui.adoc[] -**** xref:networking:azure-private-link.adoc[] -*** xref:networking:byoc/gcp/index.adoc[GCP] -**** xref:networking:byoc/gcp/vpc-peering-gcp.adoc[Add a Peering Connection] -**** xref:networking:configure-private-service-connect-in-cloud-ui.adoc[Configure Private Service Connect in the Cloud UI] -**** xref:networking:gcp-private-service-connect.adoc[Configure Private Service Connect with the Cloud API] -**** xref:networking:byoc/gcp/enable-global-access.adoc[Enable Global Access] -** xref:networking:dedicated/index.adoc[Dedicated] -*** xref:networking:dedicated/aws/index.adoc[AWS] -**** xref:networking:dedicated/aws/vpc-peering.adoc[Add a Peering Connection] -**** xref:networking:configure-privatelink-in-cloud-ui.adoc[Configure PrivateLink in the Cloud UI] -**** xref:networking:aws-privatelink.adoc[] -*** xref:networking:dedicated/azure/index.adoc[Azure] -**** xref:networking:azure-private-link-in-ui.adoc[] -**** xref:networking:azure-private-link.adoc[] -*** xref:networking:dedicated/gcp/index.adoc[GCP] -**** xref:networking:dedicated/gcp/vpc-peering-gcp.adoc[Add a Peering Connection] -**** xref:networking:dedicated/gcp/configure-psc-in-ui.adoc[Configure Private Service Connect in the Cloud UI] -**** xref:networking:dedicated/gcp/configure-psc-in-api.adoc[Configure Private Service Connect with the Cloud API] - -* xref:security:index.adoc[Security] -** xref:security:cloud-authentication.adoc[Authentication] -** xref:security:authorization/index.adoc[Authorization] -*** xref:security:authorization/cloud-authorization.adoc[Cloud Authorization] -*** xref:security:authorization/rbac/index.adoc[Role-Based Access Control (RBAC)] -**** xref:security:authorization/rbac/rbac.adoc[] -**** xref:security:authorization/rbac/rbac_dp.adoc[] -*** xref:security:authorization/rbac/acl.adoc[Access Control Lists (ACLs)] -*** xref:security:authorization/cloud-iam-policies.adoc[] -*** xref:security:authorization/cloud-iam-policies-gcp.adoc[] -*** xref:security:authorization/cloud-iam-policies-azure.adoc[] -** xref:security:cloud-encryption.adoc[Encryption] -** xref:security:cloud-availability.adoc[Availability] -** xref:security:secrets.adoc[Secrets] -** xref:security:cloud-safety-reliability.adoc[Safety and Reliability] - * xref:ai-agents:index.adoc[Agentic AI] ** xref:ai-agents:ai-gateway/index.adoc[AI Gateway] *** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[What is AI Gateway?] @@ -101,10 +55,6 @@ ***** xref:ai-agents:ai-gateway/integrations/github-copilot-admin.adoc[Admin Guide] ***** xref:ai-agents:ai-gateway/integrations/github-copilot-user.adoc[User Guide] ** xref:ai-agents:mcp/overview.adoc[MCP Overview] -** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] -*** xref:ai-agents:mcp/local/overview.adoc[Overview] -*** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] -*** xref:ai-agents:mcp/local/configuration.adoc[Configure] ** xref:ai-agents:mcp/remote/index.adoc[Remote MCP] *** xref:ai-agents:mcp/remote/overview.adoc[Overview] *** xref:ai-agents:mcp/remote/quickstart.adoc[Quickstart] @@ -118,6 +68,10 @@ **** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] **** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] *** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] +** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] +*** xref:ai-agents:mcp/local/overview.adoc[Overview] +*** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] +*** xref:ai-agents:mcp/local/configuration.adoc[Configure] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] @@ -506,6 +460,52 @@ ** xref:manage:terraform-provider.adoc[] ** xref:manage:monitor-cloud.adoc[] +* xref:networking:index.adoc[Networking] +** xref:networking:cloud-security-network.adoc[] +** xref:networking:cidr-ranges.adoc[] +** xref:networking:byoc/index.adoc[BYOC] +*** xref:networking:byoc/aws/index.adoc[AWS] +**** xref:networking:byoc/aws/vpc-peering-aws.adoc[Add a Peering Connection] +**** xref:networking:configure-privatelink-in-cloud-ui.adoc[Configure PrivateLink in the Cloud UI] +**** xref:networking:aws-privatelink.adoc[Configure PrivateLink with the Cloud API] +**** xref:networking:byoc/aws/transit-gateway.adoc[Add a Transit Gateway] +*** xref:networking:byoc/azure/index.adoc[Azure] +**** xref:networking:azure-private-link-in-ui.adoc[] +**** xref:networking:azure-private-link.adoc[] +*** xref:networking:byoc/gcp/index.adoc[GCP] +**** xref:networking:byoc/gcp/vpc-peering-gcp.adoc[Add a Peering Connection] +**** xref:networking:configure-private-service-connect-in-cloud-ui.adoc[Configure Private Service Connect in the Cloud UI] +**** xref:networking:gcp-private-service-connect.adoc[Configure Private Service Connect with the Cloud API] +**** xref:networking:byoc/gcp/enable-global-access.adoc[Enable Global Access] +** xref:networking:dedicated/index.adoc[Dedicated] +*** xref:networking:dedicated/aws/index.adoc[AWS] +**** xref:networking:dedicated/aws/vpc-peering.adoc[Add a Peering Connection] +**** xref:networking:configure-privatelink-in-cloud-ui.adoc[Configure PrivateLink in the Cloud UI] +**** xref:networking:aws-privatelink.adoc[] +*** xref:networking:dedicated/azure/index.adoc[Azure] +**** xref:networking:azure-private-link-in-ui.adoc[] +**** xref:networking:azure-private-link.adoc[] +*** xref:networking:dedicated/gcp/index.adoc[GCP] +**** xref:networking:dedicated/gcp/vpc-peering-gcp.adoc[Add a Peering Connection] +**** xref:networking:dedicated/gcp/configure-psc-in-ui.adoc[Configure Private Service Connect in the Cloud UI] +**** xref:networking:dedicated/gcp/configure-psc-in-api.adoc[Configure Private Service Connect with the Cloud API] + +* xref:security:index.adoc[Security] +** xref:security:cloud-authentication.adoc[Authentication] +** xref:security:authorization/index.adoc[Authorization] +*** xref:security:authorization/cloud-authorization.adoc[Cloud Authorization] +*** xref:security:authorization/rbac/index.adoc[Role-Based Access Control (RBAC)] +**** xref:security:authorization/rbac/rbac.adoc[] +**** xref:security:authorization/rbac/rbac_dp.adoc[] +*** xref:security:authorization/rbac/acl.adoc[Access Control Lists (ACLs)] +*** xref:security:authorization/cloud-iam-policies.adoc[] +*** xref:security:authorization/cloud-iam-policies-gcp.adoc[] +*** xref:security:authorization/cloud-iam-policies-azure.adoc[] +** xref:security:cloud-encryption.adoc[Encryption] +** xref:security:cloud-availability.adoc[Availability] +** xref:security:secrets.adoc[Secrets] +** xref:security:cloud-safety-reliability.adoc[Safety and Reliability] + * xref:billing:index.adoc[Billing] ** xref:billing:billing.adoc[] ** xref:billing:aws-commit.adoc[AWS: Use Commits] From acbbde86e35bfcf64f64faee8a1196a91f35a015 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Thu, 22 Jan 2026 12:28:36 -0700 Subject: [PATCH 26/97] update nav --- modules/ROOT/nav.adoc | 37 +++++++++++++------------- modules/ai-agents/pages/mcp/index.adoc | 10 +++++++ 2 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 modules/ai-agents/pages/mcp/index.adoc diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 0e2c153e4..6e7287438 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -54,24 +54,25 @@ **** GitHub Copilot ***** xref:ai-agents:ai-gateway/integrations/github-copilot-admin.adoc[Admin Guide] ***** xref:ai-agents:ai-gateway/integrations/github-copilot-user.adoc[User Guide] -** xref:ai-agents:mcp/overview.adoc[MCP Overview] -** xref:ai-agents:mcp/remote/index.adoc[Remote MCP] -*** xref:ai-agents:mcp/remote/overview.adoc[Overview] -*** xref:ai-agents:mcp/remote/quickstart.adoc[Quickstart] -*** xref:ai-agents:mcp/remote/concepts.adoc[Concepts] -*** xref:ai-agents:mcp/remote/create-tool.adoc[Create a Tool] -*** xref:ai-agents:mcp/remote/best-practices.adoc[Best Practices] -*** xref:ai-agents:mcp/remote/tool-patterns.adoc[Tool Patterns] -*** xref:ai-agents:mcp/remote/troubleshooting.adoc[Troubleshooting] -*** xref:ai-agents:mcp/remote/admin-guide.adoc[Admin Guide] -**** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] -**** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] -**** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] -*** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] -** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] -*** xref:ai-agents:mcp/local/overview.adoc[Overview] -*** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] -*** xref:ai-agents:mcp/local/configuration.adoc[Configure] +** xref:ai-agents:mcp/index.adoc[MCP] +*** xref:ai-agents:mcp/overview.adoc[MCP Overview] +*** xref:ai-agents:mcp/remote/index.adoc[Remote MCP] +**** xref:ai-agents:mcp/remote/overview.adoc[Overview] +**** xref:ai-agents:mcp/remote/quickstart.adoc[Quickstart] +**** xref:ai-agents:mcp/remote/concepts.adoc[Concepts] +**** xref:ai-agents:mcp/remote/create-tool.adoc[Create a Tool] +**** xref:ai-agents:mcp/remote/best-practices.adoc[Best Practices] +**** xref:ai-agents:mcp/remote/tool-patterns.adoc[Tool Patterns] +**** xref:ai-agents:mcp/remote/troubleshooting.adoc[Troubleshooting] +**** xref:ai-agents:mcp/remote/admin-guide.adoc[Admin Guide] +***** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] +***** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] +***** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] +**** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] +*** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] +**** xref:ai-agents:mcp/local/overview.adoc[Overview] +**** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] +**** xref:ai-agents:mcp/local/configuration.adoc[Configure] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] diff --git a/modules/ai-agents/pages/mcp/index.adoc b/modules/ai-agents/pages/mcp/index.adoc new file mode 100644 index 000000000..6ff198196 --- /dev/null +++ b/modules/ai-agents/pages/mcp/index.adoc @@ -0,0 +1,10 @@ += Model Context Protocol (MCP) +:description: Learn about the Model Context Protocol (MCP) in Redpanda Cloud. +:page-layout: index + +The Model Context Protocol (MCP) provides a standardized way for AI agents to connect with external data sources and tools in Redpanda Cloud. + +Redpanda Cloud offers two complementary MCP options: + +* *Remote MCP*: Deploy MCP servers directly in Redpanda Cloud for scalable, managed AI agent integrations +* *Redpanda Cloud Management MCP Server*: Connect your local AI development environment to manage Redpanda Cloud resources \ No newline at end of file From 492a50fe69d8105e4726a08761ab383cf3c87fc6 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 16:00:51 -0700 Subject: [PATCH 27/97] Refactor AI Gateway documentation for clarity and consistency Major improvements: - Rename ai-gateway.adoc to gateway-quickstart.adoc for clearer naming - Rename ai-gateway-overview.adoc to gateway-architecture.adoc - Consolidate quickstart-enhanced.adoc into gateway-quickstart.adoc - Remove 161 lines of duplicate content between what-is and architecture pages - Add page metadata (:page-topic-type:, :page-personas:, :learning-objective-N:) to all files - Convert learning objectives from bullets to required attribute format - Create BYOC version requirement partial and add to all 23 AI Gateway pages - Restructure navigation with clearer sections (Overview, Quickstart, Architecture, Observability) - Update all cross-references to renamed files This consolidation reduces content duplication, improves metadata consistency, and provides better content organization for users. Co-Authored-By: Claude Sonnet 4.5 --- modules/ROOT/nav.adoc | 15 +- .../pages/ai-gateway/admin/setup-guide.adoc | 2 +- .../pages/ai-gateway/ai-gateway-overview.adoc | 278 --------- .../pages/ai-gateway/ai-gateway.adoc | 345 ----------- .../builders/connect-your-agent.adoc | 2 + .../builders/discover-gateways.adoc | 2 + .../ai-gateway/cel-routing-cookbook.adoc | 12 +- .../ai-gateway/gateway-architecture.adoc | 158 +++++ .../pages/ai-gateway/gateway-quickstart.adoc | 583 ++++++++++++++++++ modules/ai-agents/pages/ai-gateway/index.adoc | 4 +- .../integrations/claude-code-admin.adoc | 4 +- .../integrations/claude-code-user.adoc | 12 +- .../ai-gateway/integrations/cline-admin.adoc | 4 +- .../ai-gateway/integrations/cline-user.adoc | 14 +- .../integrations/continue-admin.adoc | 4 +- .../integrations/continue-user.adoc | 12 +- .../ai-gateway/integrations/cursor-admin.adoc | 4 +- .../ai-gateway/integrations/cursor-user.adoc | 12 +- .../integrations/github-copilot-admin.adoc | 4 +- .../integrations/github-copilot-user.adoc | 12 +- .../pages/ai-gateway/integrations/index.adoc | 2 + .../ai-gateway/mcp-aggregation-guide.adoc | 14 +- .../pages/ai-gateway/migration-guide.adoc | 14 +- .../pages/ai-gateway/observability-logs.adoc | 12 +- .../ai-gateway/observability-metrics.adoc | 16 +- .../pages/ai-gateway/quickstart-enhanced.adoc | 453 -------------- .../pages/ai-gateway/what-is-ai-gateway.adoc | 15 +- .../AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md | 28 +- .../partials/ai-gateway-byoc-note.adoc | 1 + 29 files changed, 865 insertions(+), 1173 deletions(-) delete mode 100644 modules/ai-agents/pages/ai-gateway/ai-gateway-overview.adoc delete mode 100644 modules/ai-agents/pages/ai-gateway/ai-gateway.adoc create mode 100644 modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc create mode 100644 modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc delete mode 100644 modules/ai-agents/pages/ai-gateway/quickstart-enhanced.adoc create mode 100644 modules/ai-agents/partials/ai-gateway-byoc-note.adoc diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 10aab64fb..519835c9e 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -25,19 +25,20 @@ * xref:ai-agents:index.adoc[Agentic AI] ** xref:ai-agents:ai-gateway/index.adoc[AI Gateway] -*** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[AI Gateway Overview] +*** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[Overview] +*** xref:ai-agents:ai-gateway/gateway-quickstart.adoc[Quickstart] +*** xref:ai-agents:ai-gateway/gateway-architecture.adoc[Architecture] *** For Administrators **** xref:ai-agents:ai-gateway/admin/setup-guide.adoc[Setup Guide] *** For Builders **** xref:ai-agents:ai-gateway/builders/discover-gateways.adoc[Discover Gateways] **** xref:ai-agents:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] -*** Reference -**** xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[Architecture Deep Dive] -**** xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[MCP Aggregation Guide] -**** xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[CEL Routing Cookbook] +**** xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[CEL Routing Patterns] +**** xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[MCP Aggregation] +*** Observability **** xref:ai-agents:ai-gateway/observability-logs.adoc[Request Logs] -**** xref:ai-agents:ai-gateway/observability-metrics.adoc[Metrics and Usage] -**** xref:ai-agents:ai-gateway/migration-guide.adoc[Migration Guide] +**** xref:ai-agents:ai-gateway/observability-metrics.adoc[Metrics and Analytics] +*** xref:ai-agents:ai-gateway/migration-guide.adoc[Migration Guide] *** xref:ai-agents:ai-gateway/integrations/index.adoc[Integrations] **** Claude Code ***** xref:ai-agents:ai-gateway/integrations/claude-code-admin.adoc[Admin Guide] diff --git a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc index 150aab357..e3dbdee87 100644 --- a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc @@ -3,7 +3,7 @@ :page-topic-type: how-to :personas: platform_admin -NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] This guide walks administrators through the complete setup process for AI Gateway, from enabling LLM providers to configuring routing policies and MCP tool aggregation. diff --git a/modules/ai-agents/pages/ai-gateway/ai-gateway-overview.adoc b/modules/ai-agents/pages/ai-gateway/ai-gateway-overview.adoc deleted file mode 100644 index dd1ad678a..000000000 --- a/modules/ai-agents/pages/ai-gateway/ai-gateway-overview.adoc +++ /dev/null @@ -1,278 +0,0 @@ -= AI Gateway Overview -:description: Overview of Redpanda AI Gateway, its features, benefits, architecture, supported providers, deployment models, and common usage patterns. -:page-personas: app_developer, platform_admin - -NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. - -Redpanda AI Gateway is a unified access layer for LLM providers and AI tools that sits between your applications and the AI services they use. It provides centralized routing, policy enforcement, cost management, and observability for all your AI traffic. - -After reading this page, you will be able to: - -* Explain how AI Gateway centralizes LLM provider management and reduces operational complexity. -* Identify key features (routing, observability, cost controls) that address common LLM integration challenges. -* Determine whether AI Gateway fits your use case based on traffic volume and provider diversity. - -== The problem - -Modern AI applications face four critical challenges that increase costs, reduce reliability, and slow down development. - -First, applications typically hardcode provider-specific SDKs. An application using OpenAI's SDK cannot easily switch to Anthropic or Google without code changes and redeployment. This tight coupling makes testing across providers time-consuming and error-prone, and means provider outages directly impact your application availability. - -Second, costs can spiral without visibility into usage patterns. Without a centralized view of token consumption across teams and applications, it's difficult to attribute costs to specific customers, features, or environments. Testing and debugging can generate unexpected bills, and there's no way to enforce budgets or rate limits per team or customer. - -Third, AI agents that use MCP (Model Context Protocol) servers face tool coordination challenges. Managing tool discovery and execution is repetitive across projects, and agents typically load all available tools upfront, which creates high token costs. There's also no centralized governance over which tools agents can access. - -Finally, observability is fragmented across provider dashboards. You cannot reconstruct user sessions that span multiple models, compare latency and costs across providers in a unified view, or efficiently debug issues. Troubleshooting "the AI gave the wrong answer" requires manual log diving across different systems. - -== What AI Gateway solves - -Redpanda AI Gateway addresses these challenges through four core capabilities: - -=== 1. Unified LLM access (single endpoint for all providers) - -AI Gateway provides a single OpenAI-compatible endpoint that routes requests to multiple LLM providers. Instead of integrating with each provider's SDK separately, you configure your application once and switch providers by changing only the model parameter. - -// PLACEHOLDER: Add architecture diagram showing: -// - Application → AI Gateway → Multiple LLM Providers (OpenAI, Anthropic, etc.) -// - Single baseURL configuration -// - Model routing via vendor/model_id format - -Without AI Gateway, you need different SDKs and patterns for each provider: - -[source,python] ----- -# OpenAI -from openai import OpenAI -client = OpenAI(api_key="sk-...") -response = client.chat.completions.create( - model="gpt-4o", - messages=[{"role": "user", "content": "Hello"}] -) - -# Anthropic (different SDK, different patterns) -from anthropic import Anthropic -client = Anthropic(api_key="sk-ant-...") -response = client.messages.create( - model="claude-sonnet-3.5", - max_tokens=1024, - messages=[{"role": "user", "content": "Hello"}] -) ----- - -With AI Gateway, you use the OpenAI SDK for all providers: - -[source,python] ----- -from openai import OpenAI - -# Single configuration, multiple providers -client = OpenAI( - base_url="https://{GATEWAY_ENDPOINT}", - api_key="your-redpanda-token", - default_headers={"rp-aigw-id": "{GATEWAY_ID}"} -) - -# Route to OpenAI -response = client.chat.completions.create( - model="openai/gpt-4o", - messages=[{"role": "user", "content": "Hello"}] -) - -# Route to Anthropic (same code, different model string) -response = client.chat.completions.create( - model="anthropic/claude-sonnet-3.5", - messages=[{"role": "user", "content": "Hello"}] -) ----- - -To switch providers, you change only the `model` parameter from `openai/gpt-4o` to `anthropic/claude-sonnet-3.5`. No code changes or redeployment needed. - -=== 2. Policy-based routing and cost control - -AI Gateway lets you define routing rules, rate limits, and budgets once, then enforces them automatically for all requests. - -You can route requests to different models based on user attributes. For example, to direct premium users to a more capable model while routing free tier users to a cost-effective option, use a CEL expression: - -[source,cel] ----- -// Route premium users to best model, free users to cost-effective model -request.headers["x-user-tier"] == "premium" - ? "anthropic/claude-opus-4" - : "anthropic/claude-sonnet-3.5" ----- - -You can also set different rate limits and spend limits per environment to prevent staging or development traffic from consuming production budgets: - -// PLACEHOLDER: Confirm exact policy configuration format - -[source,yaml] ----- -rate_limits: - staging: 100 requests/minute - production: 10000 requests/minute - -spend_limits: - staging: $500/month - production: $50000/month ----- - -For reliability, you can configure provider pools with automatic failover. If you configure OpenAI GPT-4 as your primary model and Anthropic Claude Opus as the fallback, the gateway automatically routes requests to the fallback when it detects rate limits or timeouts from the primary provider. This configuration can achieve 99.9% uptime even during provider outages. - -// PLACEHOLDER: Add details on pool configuration and failback behavior - -=== 3. MCP aggregation and orchestration - -AI Gateway aggregates multiple MCP (Model Context Protocol) servers and provides deferred tool loading, which dramatically reduces token costs for AI agents. - -Without AI Gateway, agents typically load all available tools from multiple MCP servers at startup. This approach sends 50+ tool definitions with every request, creating high token costs (thousands of tokens per request), slow agent startup times, and no centralized governance over which tools agents can access. - -With AI Gateway, you configure approved MCP servers once, and the gateway loads only search and orchestrator tools initially. Agents query for specific tools only when needed, which reduces token usage by 80-90% depending on your configuration. You also gain centralized approval and governance over which MCP servers your agents can access. - -For complex workflows, AI Gateway provides a JavaScript-based orchestrator tool that reduces multi-step workflows from multiple round trips to a single call. For example, you can create a workflow that searches a vector database and, if the results are insufficient, falls back to web search—all in one orchestration step. - -=== 4. Unified observability and cost tracking - -AI Gateway provides a single dashboard that tracks all LLM traffic across providers, eliminating the need to switch between multiple provider dashboards. - -// PLACEHOLDER: Add screenshots of: -// - Request logs view -// - Cost breakdown by model/provider -// - Latency histogram -// - Error rate tracking - -The dashboard tracks request volume per gateway, model, and provider, along with token usage for both prompt and completion tokens. You can view estimated spend per model with cross-provider comparisons, latency metrics (p50, p95, p99), and errors broken down by type, provider, and model. - -This unified view helps you answer critical questions such as which model is the most cost-effective for your use case, why a specific user request failed, how much your staging environment costs per week, and what the latency difference is between providers for your workload. - -== Common gateway patterns - -=== Team isolation - -When multiple teams share infrastructure but need separate budgets and policies, create one gateway per team. For example, you might configure Team A's gateway with a $5K/month budget for both staging and production environments, while Team B's gateway has a $10K/month budget with different rate limits. Each team sees only their own traffic in the observability dashboards, providing clear cost attribution and isolation. - -=== Environment separation - -To prevent staging traffic from affecting production metrics, create separate gateways for each environment. Configure the staging gateway with lower rate limits, restricted model access, and aggressive cost controls to prevent runaway expenses. The production gateway can have higher rate limits, access to all models, and alerting configured to detect anomalies. - -=== Primary and fallback for reliability - -To ensure uptime during provider outages, configure provider pools with automatic failover. For example, you can set OpenAI as your primary provider (preferred for quality) and configure Anthropic as the fallback that activates when the gateway detects rate limits or timeouts from OpenAI. Monitor the fallback rate to detect primary provider issues early, before they impact your users. - -=== A/B testing models -To compare model quality and cost without dual integration, route a percentage of traffic to different models. For example, you can send 80% of traffic to `claude-sonnet-3.5` and 20% to `claude-opus-4`, then compare quality metrics and costs in the observability dashboard before adjusting the split. - -// PLACEHOLDER: Confirm if percentage-based routing is supported, or if it's header-based only - -=== Customer-based routing - -For SaaS products with tiered pricing (free, pro, enterprise), use CEL routing based on request headers to match users with appropriate models: - -[source,cel] ----- -request.headers["x-customer-tier"] == "enterprise" ? "anthropic/claude-opus-4" : -request.headers["x-customer-tier"] == "pro" ? "anthropic/claude-sonnet-3.5" : -"anthropic/claude-haiku" ----- - -== What's supported today - -LLM providers - -* OpenAI -* Anthropic -* // PLACEHOLDER: Google, AWS Bedrock, Azure OpenAI, others? - -API compatibility - -* OpenAI-compatible `/v1/chat/completions` endpoint -* // PLACEHOLDER: Streaming support? -* // PLACEHOLDER: Embeddings support? -* // PLACEHOLDER: Other endpoints? - -Policy features - -* CEL-based routing expressions -* Rate limiting (// PLACEHOLDER: per-gateway, per-header, per-tenant?) -* Monthly spend limits (// PLACEHOLDER: per-gateway, per-workspace?) -* Provider pools with automatic failover -* // PLACEHOLDER: Caching support? - -MCP support - -* MCP server aggregation -* Deferred tool loading (80-90% token reduction) -* JavaScript orchestrator for multi-step workflows -* // PLACEHOLDER: Tool execution sandboxing? - -Observability - -* Request logs with full prompt/response history -* Token usage tracking -* Estimated cost per request -* Latency metrics -* // PLACEHOLDER: Metrics export? OpenTelemetry support? - -// What's not supported yet -// PLACEHOLDER: List current limitations, for example: -// - Custom model deployments (Azure OpenAI BYOK, AWS Bedrock custom models) -// - Response caching -// - Prompt templates/versioning -// - Guardrails (PII detection, content moderation) -// - Multi-region active-active deployment -// - Metrics export to external systems -// - Budget alerts/notifications - -== Architecture - -AI Gateway consists of three planes: a control plane for configuration and management, a data plane for request processing and routing, and an observability plane for monitoring and analytics. - -// PLACEHOLDER: Add architecture diagram showing: -// 1. Control Plane: -// - Workspace management -// - Provider/model configuration -// - Gateway creation and policy definition -// - Admin console -// -// 2. Data Plane: -// - Request ingestion -// - Policy evaluation (rate limits → spend limits → routing → execution) -// - Provider pool selection and failover -// - MCP aggregation layer -// - Response logging and metrics -// -// 3. Observability Plane: -// - Request logs storage -// - Metrics aggregation -// - Dashboard UI - -When a request flows through AI Gateway, it passes through several policy and routing stages before reaching the LLM provider. Understanding this lifecycle helps you configure policies effectively and troubleshoot issues: - -. Application sends request to gateway endpoint with `rp-aigw-id` header -. Gateway authenticates request -. Rate limit policy evaluates (allow/deny) -. Spend limit policy evaluates (allow/deny) -. Routing policy evaluates (which model/provider to use) -. Provider pool selects backend (primary/fallback) -. Request forwarded to LLM provider -. Response returned to application -. Request logged with tokens, cost, latency, status - -Each policy evaluation happens synchronously in the request path. If rate limits or spend limits reject the request, the gateway returns an error immediately without calling the LLM provider, which helps you control costs. - -For MCP tool requests, the lifecycle differs slightly to support deferred tool loading: - -. Application discovers tools via `/mcp` endpoint -. Gateway aggregates tools from approved MCP servers -. Application receives search + orchestrator tools (deferred loading) -. Application invokes specific tool -. Gateway routes to appropriate MCP server -. Tool execution result returned -. Request logged with execution time, status - -The gateway only loads and exposes specific tools when requested, which dramatically reduces the token overhead compared to loading all tools upfront. - -== Next steps - -* xref:ai-agents:ai-gateway/ai-gateway.adoc[]: Route your first request through AI Gateway. -* xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure MCP server aggregation for AI agents. -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor request logs, token usage, and costs. diff --git a/modules/ai-agents/pages/ai-gateway/ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway/ai-gateway.adoc deleted file mode 100644 index e8b0becb9..000000000 --- a/modules/ai-agents/pages/ai-gateway/ai-gateway.adoc +++ /dev/null @@ -1,345 +0,0 @@ -= AI Gateway Quickstart -:description: Quickstart to configure the AI Gateway for unified access to multiple LLM providers and MCP servers through a single endpoint. - - -NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. - -The Redpanda AI Gateway is a production-grade proxy that provides unified access to multiple Large Language Model (LLM) providers and Model Context Protocol (MCP) servers through a single endpoint. MCP servers expose tools that agents can discover and call. An AI Gateway maintains centralized control over routing, rate limiting, cost optimization, security, and observability. - -== Prerequisites - -* Access to the AI Gateway UI (provided by your administrator) -* API key for at least one LLM provider: OpenAI or Anthropic -* Optional: MCP server endpoints if you plan to use tool aggregation - -== Get started - -Before you can create a gateway, an administrator must enable LLM providers and models. - -=== Step 1: Enable a provider - -Providers represent upstream services (Anthropic, OpenAI) and associated credentials. Providers are disabled by default. An administrator must enable them explicitly by adding credentials. - -. In AI Gateways, navigate to *Providers*. -. Select a provider (for example, Anthropic). -. On the *Configuration* tab for the provider, click *Add configuration* and enter your API Key. - -=== Step 2: Enable models - -The model catalog is the set of models made available through the gateway. Models are disabled by default. After enabling a provider, an administrator can enable its models. - -The infrastructure that is serving the model is different based on the provider you select. For example, OpenAI has different reliability and availability metrics than Anthropic. When you consider all the metrics, you can design your gateway to use different providers for different use cases. - -. Navigate to *Models*. -. Enable the models you want exposed through gateways. - -==== Model naming convention - -Model provider requests must use the `vendor/model_id` format in the model property of the request body, and include the `rp-aigw-id` header with the gateway ID the request is being sent to. The following example routes OpenAI API calls through Redpanda's AI Gateway for centralized control. - -[source,python] ----- -# Example: Using the OpenAI Python SDK with AI Gateway -from openai import OpenAI - -client = OpenAI( - base_url="https://gw.ai.panda.com", <1> - api_key="", -) - -# Add header per request -response = client.chat.completions.create( - model="openai/gpt-5", <2> - messages=[{"role": "user", "content": "Hello!"}], - extra_headers={ - "rp-aigw-id": "gateway-abc" # Override for this request - } <3> -) ----- -<1> This redirects the OpenAI client to the AI Gateway endpoint. -<2> The `model` property uses the `vendor/model_id` format as required by the AI Gateway. -<3> Includes the `rp-aigw-id` header to specify which gateway configuration to use. - -=== Step 3: Create a gateway - -A gateway is a logical configuration boundary (policies + routing + observability) on top of a single deployment. It's a "virtual gateway" that you can create per team, environment (staging/production), product, or customer. - -. Navigate to *Gateways*. -. Click *Create Gateway*. -. Choose a name, workspace, and optional metadata. -+ -TIP: A _workspace_ is conceptually similar to a _resource group_ in Redpanda streaming. - -. After creation, copy the *Gateway Endpoint* from the gateway detail page. - -=== Step 4: Configure LLM routing - -On the Gateways page, select the *LLM* tab to configure rate limits, spend limits, routing, and provider pools with fallback options. - -The LLM routing pipeline visually represents the request lifecycle: - -. Rate Limit: For example, global rate limit of 100 requests/second. -. Spend Limit / Monthly Budget: For example, $15K/month with blocking enforcement, so it blocks requests after that budget is exceeded. -. Routing to a primary provider pool with optional fallback provider pools: For example, primary route to Anthropic backend pool, and if that fails, it will fallback to OpenAI pool. - -*Load balancing / multi-provider distribution:* -If a provider pool contains multiple providers, you can distribute traffic (for example, balancing across Anthropic and OpenAI). - -TIP: Provider pool (UI) = Backend pool (API) - -=== Step 5: Configure MCP tools - -On the Gateways page, select the *MCP* tab to configure your MCP tool discovery and tool execution. This MCP proxy is an aggregator of MCP servers, allowing multiple MCP servers behind a single endpoint. Agents can then find tools and call them through the gateway. To configure the MCP proxy, add the following: - -* Display name: When you drag a provider pool, you give it a name. -* Model dropdown: Choose a model from the available models in the catalog. -* Load balancing options: If you have multiple providers, you can load balance requests between them; for example, round robin. - -MCP tools include a data catalog API, the memory store, a vector search service, and an MCP orchestrator. The *MCP orchestrator* is a built-in MCP server that enables programmatic tool calling. Agents can generate code to call multiple tools in a single orchestrated step, which reduces the number of round trips. For example, a workflow requiring 47 file reads can be reduced from 49 round trips to just 1. To add other tools, (for example, Slack), add the Slack MCP server endpoint. - -When many tools are aggregated, listing all tools can consume significant tokens. With *deferred tool loading*, instead of returning all tools, the MCP gateway initially returns a tool search capability and the MCP orchestrator. The agent then searches for the specific tool it needs and retrieves only that subset. That way, the exchange of messages between the MCP gateway and the agent is small. This can reduce token usage significantly when you have many tools configured. - -*REVIEWERS: When/how exactly do you use the orchestrator? Also what happens after they create a gateway? Please provide an example of how to validate end-to-end routing against the gateway endpoint!* - -*REVIEWERS: How do users connect to the ADP catalog + MCP servers exposed through RPCN?* - -== Observability - -After traffic flows through a gateway, you can inspect: - -* Request volume -* Token usage -* Estimated spend -* Latency -* Per-model breakdown - -This is central to governance: You can see and control usage by gateway boundary (for example, by team, environment, customer, or product). - -*REVIEWERS: Where do those metrics appear in the UI, or how does a user validate observability after setup?* - -== CEL routing - -The AI Gateway uses Common Expression Language (CEL) for flexible routing and policy application. CEL expressions let you create sophisticated routing rules based on request properties without code changes. Use CEL to: - -* Route requests to specific providers based on model family -* Apply different rate limits based on user tiers -* Enforce policies based on request content - -The editor in the UI helps you discover available request fields (headers, path, body, and so on). - -=== CEL examples - -Route based on model family: - -[source,cel] ----- -request.body.model.startsWith("anthropic/") ----- - -Apply a rule to all requests: - -[source,cel] ----- -true ----- - -Route based on a header (for example, product tier): - -[source,cel] ----- -request.headers['tier'][0] == "premium" ----- - -Guard for field existence: - -[source,cel] ----- -has(request.body.max_tokens) && request.body.max_tokens > 1000 ----- - -== Integrate with AI agents and tools - -The AI Gateway provides standardized endpoints that work with various AI development tools and agents. This section shows how to configure popular tools to use your AI Gateway endpoints. - -=== MCP server endpoint - -If you've configured MCP tools in your gateway, AI agents can connect to the aggregated MCP endpoint: - -* MCP endpoint URL: `https://gw.ai.panda.com/mcp` - -* Headers required: -** `Authorization: Bearer ` -** `rp-aigw-id: ` - -This endpoint aggregates all MCP servers configured in your gateway, providing a unified interface for tool discovery and execution. - -=== Environment variables - -For consistent configuration across tools, set these environment variables: - -[source,bash] ----- -export REDPANDA_GATEWAY_URL="https://gw.ai.panda.com" -export REDPANDA_GATEWAY_ID="" -export REDPANDA_API_KEY="" ----- - -Many tools and SDKs can automatically use these environment variables when configured appropriately. - -=== Claude Code - -Configure Claude Code to use AI Gateway endpoints using HTTP transport for the MCP connection. - -*For Claude Code CLI:* - -Use the `claude mcp add` command to configure the HTTP transport: - -[source,bash] ----- -claude mcp add --transport http redpanda-aigateway https://gw.ai.panda.com/mcp \ - --header "Authorization: Bearer " \ - --header "rp-aigw-id: " ----- - -*Alternative configuration via config file:* - -Create or edit `~/.claude/config.json`: - -[source,json] ----- -{ - "mcpServers": { - "redpanda-ai-gateway": { - "transport": "http", - "url": "https://gw.ai.panda.com/mcp", - "headers": { - "Authorization": "Bearer ", - "rp-aigw-id": "" - } - } - }, - "apiProviders": { - "redpanda": { - "baseURL": "https://gw.ai.panda.com", - "headers": { - "rp-aigw-id": "" - } - } - } -} ----- - -=== VS Code extensions - -Configure VS Code extensions that support OpenAI-compatible APIs: - -*Continue extension:* - -Edit your Continue config file (`~/.continue/config.json`): - -[source,json] ----- -{ - "models": [ - { - "title": "Redpanda AI Gateway - GPT-4", - "provider": "openai", - "model": "openai/gpt-4", - "apiBase": "https://gw.ai.panda.com", - "apiKey": "", - "requestOptions": { - "headers": { - "rp-aigw-id": "" - } - } - }, - { - "title": "Redpanda AI Gateway - Claude", - "provider": "anthropic", - "model": "anthropic/claude-3-5-sonnet-20241022", - "apiBase": "https://gw.ai.panda.com", - "apiKey": "", - "requestOptions": { - "headers": { - "rp-aigw-id": "" - } - } - } - ] -} ----- - -=== Cursor IDE - -Configure Cursor to route requests through the AI Gateway: - -. Open Cursor Settings (*Cursor* → *Settings* or `Cmd+,`) -. Navigate to *AI* settings -. Add a custom OpenAI-compatible provider: - -[source,json] ----- -{ - "cursor.ai.providers.openai.apiBase": "https://gw.ai.panda.com", - "cursor.ai.providers.openai.defaultHeaders": { - "rp-aigw-id": "" - } -} ----- - -=== Custom applications - -For custom applications using OpenAI or Anthropic SDKs: - -*OpenAI SDK (Python):* - -[source,python] ----- -from openai import OpenAI - -client = OpenAI( - base_url="https://gw.ai.panda.com", - api_key="", - default_headers={ - "rp-aigw-id": "" - } -) ----- - -*Anthropic SDK (Python):* - -[source,python] ----- -from anthropic import Anthropic - -client = Anthropic( - base_url="https://gw.ai.panda.com", - api_key="", - default_headers={ - "rp-aigw-id": "" - } -) ----- - -*Node.js with OpenAI SDK:* - -[source,javascript] ----- -import OpenAI from 'openai'; - -const openai = new OpenAI({ - baseURL: 'https://gw.ai.panda.com', - apiKey: process.env.OPENAI_API_KEY, - defaultHeaders: { - 'rp-aigw-id': '' - } -}); ----- - -== Next steps - -* xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[]: Learn about AI Gateway architecture, deployment models, and common usage patterns. -* xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Explore advanced CEL routing patterns for traffic distribution, cost optimization, and failover. -* xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure MCP server aggregation and deferred tool loading for AI agents. -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor request logs, token usage, and costs through the observability dashboard. -* xref:ai-agents:ai-gateway/integrations/index.adoc[]: Connect AI development tools like Claude Code, Cursor, and Continue to your gateway. \ No newline at end of file diff --git a/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc b/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc index 44a439d11..7abe34f00 100644 --- a/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc +++ b/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc @@ -3,6 +3,8 @@ :page-topic-type: how-to :personas: app_developer +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + This guide shows you how to connect your AI agent or application to a Redpanda AI Gateway. You'll configure your client SDK, make your first request, and validate the integration. After completing this guide, you will be able to: diff --git a/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc b/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc index 43890fa5f..01db50d20 100644 --- a/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc +++ b/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc @@ -3,6 +3,8 @@ :page-topic-type: how-to :personas: app_developer +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + As a builder, you need to know which gateways are available to you before integrating your agent or application. This page shows you how to discover accessible gateways, understand their configurations, and verify connectivity. After reading this page, you will be able to: diff --git a/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc index ce12259cc..3d2ee3b0b 100644 --- a/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc +++ b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc @@ -1,14 +1,14 @@ = CEL Routing Cookbook :description: CEL routing cookbook for Redpanda AI Gateway with common patterns, examples, and best practices. +:page-topic-type: cookbook :page-personas: app_developer, platform_admin +:learning-objective-1: Write CEL expressions to route requests based on user tier or custom headers +:learning-objective-2: Test CEL routing logic using the UI editor or test requests +:learning-objective-3: Troubleshoot common CEL errors using safe patterns -Redpanda AI Gateway uses CEL (Common Expression Language) for dynamic request routing. CEL expressions evaluate request properties (headers, body, context) and determine which model or provider should handle each request. - -After reading this page, you will be able to: +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] -* Write CEL expressions to route requests based on user tier, environment, content complexity, or custom headers. -* Test CEL routing logic using the UI editor or test requests to verify expected model selection. -* Troubleshoot common CEL errors (type mismatches, missing fields, index out of bounds) using safe patterns. +Redpanda AI Gateway uses CEL (Common Expression Language) for dynamic request routing. CEL expressions evaluate request properties (headers, body, context) and determine which model or provider should handle each request. CEL enables: diff --git a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc new file mode 100644 index 000000000..99c869948 --- /dev/null +++ b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc @@ -0,0 +1,158 @@ += AI Gateway Architecture +:description: Technical architecture of Redpanda AI Gateway, including request lifecycle, supported providers, deployment models, and implementation details. +:page-topic-type: concept +:page-personas: app_developer, platform_admin +:learning-objective-1: Describe the three architectural planes of AI Gateway +:learning-objective-2: Explain the request lifecycle through policy evaluation stages +:learning-objective-3: Identify supported providers, features, and current limitations + +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + +This page provides technical details about AI Gateway's architecture, request processing, and capabilities. For an introduction to AI Gateway and the problems it solves, see xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[]. + +== Architecture overview + +AI Gateway consists of three planes: a control plane for configuration and management, a data plane for request processing and routing, and an observability plane for monitoring and analytics. + +// PLACEHOLDER: Add architecture diagram showing: +// 1. Control Plane: +// - Workspace management +// - Provider/model configuration +// - Gateway creation and policy definition +// - Admin console +// +// 2. Data Plane: +// - Request ingestion +// - Policy evaluation (rate limits → spend limits → routing → execution) +// - Provider pool selection and failover +// - MCP aggregation layer +// - Response logging and metrics +// +// 3. Observability Plane: +// - Request logs storage +// - Metrics aggregation +// - Dashboard UI + +=== Control plane + +The control plane manages gateway configuration and policy definition: + +* **Workspace management**: Multi-tenant isolation with separate namespaces for different teams or environments +* **Provider configuration**: Enable and configure LLM providers (OpenAI, Anthropic, etc.) +* **Gateway creation**: Define gateways with specific routing rules, budgets, and rate limits +* **Policy definition**: Create CEL-based routing policies, spend limits, and rate limits +* **MCP server registration**: Configure which MCP servers are available to agents + +=== Data plane + +The data plane handles all runtime request processing: + +* **Request ingestion**: Accept requests via OpenAI-compatible API endpoints +* **Authentication**: Validate API keys and gateway access +* **Policy evaluation**: Apply rate limits, spend limits, and routing policies +* **Provider pool management**: Select primary or fallback providers based on availability +* **MCP aggregation**: Aggregate tools from multiple MCP servers with deferred loading +* **Response transformation**: Normalize provider-specific responses to OpenAI format +* **Metrics collection**: Record token usage, latency, and cost for every request + +=== Observability plane + +The observability plane provides monitoring and analytics: + +* **Request logs**: Store full request/response history with prompt and completion content +* **Metrics aggregation**: Calculate token usage, costs, latency percentiles, and error rates +* **Dashboard UI**: Display real-time and historical analytics per gateway, model, or provider +* **Cost tracking**: Estimate spend based on provider pricing and token consumption + +== Request lifecycle + +When a request flows through AI Gateway, it passes through several policy and routing stages before reaching the LLM provider. Understanding this lifecycle helps you configure policies effectively and troubleshoot issues: + +. Application sends request to gateway endpoint with `rp-aigw-id` header +. Gateway authenticates request +. Rate limit policy evaluates (allow/deny) +. Spend limit policy evaluates (allow/deny) +. Routing policy evaluates (which model/provider to use) +. Provider pool selects backend (primary/fallback) +. Request forwarded to LLM provider +. Response returned to application +. Request logged with tokens, cost, latency, status + +Each policy evaluation happens synchronously in the request path. If rate limits or spend limits reject the request, the gateway returns an error immediately without calling the LLM provider, which helps you control costs. + +=== MCP tool request lifecycle + +For MCP tool requests, the lifecycle differs slightly to support deferred tool loading: + +. Application discovers tools via `/mcp` endpoint +. Gateway aggregates tools from approved MCP servers +. Application receives search + orchestrator tools (deferred loading) +. Application invokes specific tool +. Gateway routes to appropriate MCP server +. Tool execution result returned +. Request logged with execution time, status + +The gateway only loads and exposes specific tools when requested, which dramatically reduces the token overhead compared to loading all tools upfront. + +== Supported features + +=== LLM providers + +* OpenAI +* Anthropic +* // PLACEHOLDER: Google, AWS Bedrock, Azure OpenAI, others? + +=== API compatibility + +* OpenAI-compatible `/v1/chat/completions` endpoint +* // PLACEHOLDER: Streaming support? +* // PLACEHOLDER: Embeddings support? +* // PLACEHOLDER: Other endpoints? + +=== Policy features + +* CEL-based routing expressions +* Rate limiting (// PLACEHOLDER: per-gateway, per-header, per-tenant?) +* Monthly spend limits (// PLACEHOLDER: per-gateway, per-workspace?) +* Provider pools with automatic failover +* // PLACEHOLDER: Caching support? + +=== MCP support + +* MCP server aggregation +* Deferred tool loading (80-90% token reduction) +* JavaScript orchestrator for multi-step workflows +* // PLACEHOLDER: Tool execution sandboxing? + +=== Observability + +* Request logs with full prompt/response history +* Token usage tracking +* Estimated cost per request +* Latency metrics +* // PLACEHOLDER: Metrics export? OpenTelemetry support? + +== Current limitations + +// PLACEHOLDER: List current limitations, for example: +// - Custom model deployments (Azure OpenAI BYOK, AWS Bedrock custom models) +// - Response caching +// - Prompt templates/versioning +// - Guardrails (PII detection, content moderation) +// - Multi-region active-active deployment +// - Metrics export to external systems +// - Budget alerts/notifications + +== Deployment models + +// PLACEHOLDER: Add deployment model details: +// - BYOC deployment requirements +// - Scaling characteristics +// - High availability configuration +// - Regional deployment options + +== Next steps + +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[]: Route your first request through AI Gateway +* xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure MCP server aggregation for AI agents +* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor request logs, token usage, and costs diff --git a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc new file mode 100644 index 000000000..dd1505fe1 --- /dev/null +++ b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc @@ -0,0 +1,583 @@ += AI Gateway Quickstart +:description: Get started with AI Gateway by configuring providers, creating your first gateway, and routing requests through unified LLM endpoints. +:page-topic-type: quickstart +:page-personas: app_developer, platform_admin +:learning-objective-1: Enable an LLM provider and create your first gateway +:learning-objective-2: Route your first request through AI Gateway and verify it works +:learning-objective-3: View request logs and token usage in the observability dashboard + +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + +Redpanda AI Gateway provides unified access to multiple Large Language Model (LLM) providers and Model Context Protocol (MCP) servers through a single endpoint. This quickstart walks you through configuring your first gateway and routing requests through it. + +== Prerequisites + +Before starting, ensure you have: + +* Access to the AI Gateway UI (provided by your administrator) +* Admin permissions to configure providers and gateways +* API key for at least one LLM provider (OpenAI or Anthropic) +* Python 3.8+, Node.js 18+, or cURL (for testing) + +== Step 1: Configure a provider + +Providers represent upstream LLM services (OpenAI, Anthropic) and their associated credentials. Providers are disabled by default and must be enabled explicitly. + +. In AI Gateways, navigate to *Providers*. +. Select a provider (for example, OpenAI or Anthropic). +. On the *Configuration* tab, click *Add configuration* and enter your API Key. +. Verify the provider status shows "Active". + +AI Gateway currently supports: + +* OpenAI +* Anthropic + +== Step 2: Enable models + +After enabling a provider, enable the specific models you want to make available through your gateways. + +. Navigate to *Models*. +. Enable the models you want to use (for example, `gpt-4o`, `gpt-4o-mini`, `claude-sonnet-3.5`). +. Verify the models appear as "Enabled" in the catalog. + +TIP: Different providers have different reliability and cost characteristics. When choosing models, consider your use case requirements for quality, speed, and cost. + +=== Model naming convention + +Requests through AI Gateway must use the `vendor/model_id` format. For example: + +* OpenAI models: `openai/gpt-4o`, `openai/gpt-4o-mini` +* Anthropic models: `anthropic/claude-sonnet-3.5`, `anthropic/claude-opus-4` + +This format allows the gateway to route requests to the correct provider. + +== Step 3: Create a gateway + +A gateway is a logical configuration boundary that defines routing policies, rate limits, spend limits, and observability scope. You can create separate gateways per team, environment (staging/production), or customer. + +. Navigate to *Gateways*. +. Click *Create Gateway*. +. Configure the gateway: ++ +* *Name*: Choose a descriptive name (for example, `my-first-gateway`) +* *Workspace*: Select a workspace (conceptually similar to a resource group) +* *Description*: Optional metadata for documentation + +. After creation, copy the *Gateway Endpoint* and *Gateway ID* from the gateway detail page. You'll need these for sending requests. + +Your gateway endpoint format: +---- +Gateway Endpoint: https://gw.ai.panda.com +Gateway ID: gw_abc123... +---- + +Common gateway patterns: + +* *Environment separation*: Create separate gateways for staging and production +* *Team isolation*: One gateway per team for budget tracking +* *Customer multi-tenancy*: One gateway per customer for isolated policies + +== Step 4: Send your first request + +Now that you've configured a provider and created a gateway, send a test request to verify everything works. + +[tabs] +==== +Python:: ++ +-- +[source,python] +---- +from openai import OpenAI + +# Configure client to use AI Gateway +client = OpenAI( + base_url="https://gw.ai.panda.com", # Your gateway endpoint + api_key="", # Your Redpanda API key + default_headers={ + "rp-aigw-id": "gw_abc123..." # Your gateway ID + } +) + +# Send a request (note the vendor/model_id format) +response = client.chat.completions.create( + model="openai/gpt-4o-mini", # Format: {provider}/{model} + messages=[ + {"role": "user", "content": "Say 'Hello from AI Gateway!'"} + ], + max_tokens=20 +) + +print(response.choices[0].message.content) +# Expected output: Hello from AI Gateway! +---- +-- + +TypeScript/JavaScript:: ++ +-- +[source,typescript] +---- +import OpenAI from 'openai'; + +const client = new OpenAI({ + baseURL: 'https://gw.ai.panda.com', + apiKey: process.env.REDPANDA_API_KEY, + defaultHeaders: { + 'rp-aigw-id': 'gw_abc123...' + } +}); + +const response = await client.chat.completions.create({ + model: 'openai/gpt-4o-mini', + messages: [ + { role: 'user', content: 'Say "Hello from AI Gateway!"' } + ], + max_tokens: 20 +}); + +console.log(response.choices[0].message.content); +// Expected output: Hello from AI Gateway! +---- +-- + +cURL:: ++ +-- +[source,bash] +---- +curl https://gw.ai.panda.com/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${REDPANDA_API_KEY}" \ + -H "rp-aigw-id: gw_abc123..." \ + -d '{ + "model": "openai/gpt-4o-mini", + "messages": [ + {"role": "user", "content": "Say \"Hello from AI Gateway!\""} + ], + "max_tokens": 20 + }' +---- + +Expected response: + +[source,json] +---- +{ + "id": "chatcmpl-...", + "object": "chat.completion", + "created": 1704844800, + "model": "openai/gpt-4o-mini", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello from AI Gateway!" + }, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 8, + "completion_tokens": 5, + "total_tokens": 13 + } +} +---- +-- +==== + +=== Troubleshooting + +If your request fails, check these common issues: + +* *401 Unauthorized*: Verify your API key is valid +* *404 Not Found*: Confirm the base URL matches your gateway endpoint +* *Model not found*: Ensure the model is enabled in Step 2 +* *Missing rp-aigw-id*: Add the gateway ID header to your request + +== Step 5: Verify in observability dashboard + +Confirm your request appears in the AI Gateway observability dashboard. + +// PLACEHOLDER: Add UI navigation path + +. Navigate to the observability dashboard for your gateway. +. Filter by: ++ +* *Gateway*: `my-first-gateway` +* *Model*: `openai/gpt-4o-mini` +* *Time range*: Last 5 minutes + +. Verify the request log shows: ++ +* *Model*: `openai/gpt-4o-mini` +* *Provider*: OpenAI +* *Status*: 200 (success) +* *Prompt tokens*: ~8 +* *Completion tokens*: ~5 +* *Estimated cost*: Based on provider pricing +* *Latency*: Response time in milliseconds + +. Click the request to expand and view: ++ +* Full prompt and response content +* Request headers +* Routing decision details + +If your request doesn't appear: + +* Wait a few seconds for logs to populate (there may be a brief delay) +* Verify the gateway ID in your request matches the gateway you're viewing +* Check that your client received a successful response + +== Step 6: Configure LLM routing (optional) + +Configure rate limits, spend limits, and provider pools with failover. + +On the Gateways page, select the *LLM* tab to configure routing policies. The LLM routing pipeline represents the request lifecycle: + +. *Rate Limit*: Control request throughput (for example, 100 requests/second) +. *Spend Limit*: Set monthly budget caps (for example, $15K/month with blocking enforcement) +. *Provider Pools*: Define primary and fallback providers + +=== Configure provider pool with fallback + +For high availability, configure a fallback provider that activates when the primary fails: + +. Add a second provider (for example, Anthropic) following Step 1. +. In your gateway's *LLM* routing configuration: ++ +* *Primary pool*: OpenAI (preferred for quality) +* *Fallback pool*: Anthropic (activates on rate limits, timeouts, or errors) + +. Save the configuration. + +The gateway automatically routes to the fallback when it detects: + +* Rate limit exceeded +* Request timeout +* 5xx server errors from primary provider + +Monitor the fallback rate in observability to detect primary provider issues early. + +== Step 7: Configure MCP tools (optional) + +If you're using AI agents, configure MCP (Model Context Protocol) tool aggregation. + +On the Gateways page, select the *MCP* tab to configure tool discovery and execution. The MCP proxy aggregates multiple MCP servers behind a single endpoint, allowing agents to discover and call tools through the gateway. + +Configure the MCP settings: + +* *Display name*: Descriptive name for the provider pool +* *Model*: Choose which model handles tool execution +* *Load balancing*: If multiple providers are available, select a strategy (for example, round robin) + +=== Available MCP tools + +The gateway provides these built-in MCP tools: + +* *Data catalog API*: Query your data catalog +* *Memory store*: Persistent storage for agent state +* *Vector search*: Semantic search over embeddings +* *MCP orchestrator*: Built-in tool for programmatic multi-tool workflows + +The *MCP orchestrator* enables agents to generate JavaScript code that calls multiple tools in a single orchestrated step, reducing round trips. For example, a workflow requiring 47 file reads can be reduced from 49 round trips to just 1. + +To add external tools (for example, Slack, GitHub), add their MCP server endpoints to your gateway configuration. + +=== Deferred tool loading + +When many tools are aggregated, listing all tools upfront can consume significant tokens. With *deferred tool loading*, the MCP gateway initially returns only: + +* A tool search capability +* The MCP orchestrator + +Agents then search for specific tools they need, retrieving only that subset. This can reduce token usage by 80-90% when you have many tools configured. + +// REVIEWERS: When/how exactly do you use the orchestrator? Also what happens after they create a gateway? Please provide an example of how to validate end-to-end routing against the gateway endpoint! + +// REVIEWERS: How do users connect to the ADP catalog + MCP servers exposed through RPCN? + +== Step 8: Create CEL routing rule (optional) + +Use CEL (Common Expression Language) expressions to route requests dynamically based on headers, content, or other request properties. + +The AI Gateway uses CEL for flexible routing without code changes. Use CEL to: + +* Route premium users to better models +* Apply different rate limits based on user tiers +* Enforce policies based on request content + +=== Add a routing rule + +In your gateway's routing configuration: + +. Add a CEL expression to route based on user tier: ++ +[source,cel] +---- +# Route based on user tier header +request.headers["x-user-tier"] == "premium" + ? "openai/gpt-4o" + : "openai/gpt-4o-mini" +---- + +. Save the rule. + +The gateway editor helps you discover available request fields (headers, path, body, and so on). + +=== Test the routing rule + +Send requests with different headers to verify routing: + +*Premium user request*: + +[source,python] +---- +response = client.chat.completions.create( + model="openai/gpt-4o", # Will be routed based on CEL rule + messages=[{"role": "user", "content": "Hello"}], + extra_headers={"x-user-tier": "premium"} +) +# Should route to gpt-4o (premium model) +---- + +*Free user request*: + +[source,python] +---- +response = client.chat.completions.create( + model="openai/gpt-4o-mini", + messages=[{"role": "user", "content": "Hello"}], + extra_headers={"x-user-tier": "free"} +) +# Should route to gpt-4o-mini (cost-effective model) +---- + +Check the observability dashboard to verify: + +* The correct model was selected based on the header value +* The routing decision explanation shows which CEL rule matched + +=== Common CEL patterns + +Route based on model family: + +[source,cel] +---- +request.body.model.startsWith("anthropic/") +---- + +Apply a rule to all requests: + +[source,cel] +---- +true +---- + +Guard for field existence: + +[source,cel] +---- +has(request.body.max_tokens) && request.body.max_tokens > 1000 +---- + +For more CEL examples, see xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]. + +== Connect AI tools to your gateway + +The AI Gateway provides standardized endpoints that work with various AI development tools. This section shows how to configure popular tools. + +=== MCP endpoint + +If you've configured MCP tools in your gateway, AI agents can connect to the aggregated MCP endpoint: + +* *MCP endpoint URL*: `https://gw.ai.panda.com/mcp` +* *Required headers*: +** `Authorization: Bearer ` +** `rp-aigw-id: ` + +This endpoint aggregates all MCP servers configured in your gateway. + +=== Environment variables + +For consistent configuration, set these environment variables: + +[source,bash] +---- +export REDPANDA_GATEWAY_URL="https://gw.ai.panda.com" +export REDPANDA_GATEWAY_ID="" +export REDPANDA_API_KEY="" +---- + +=== Claude Code + +Configure Claude Code using HTTP transport for the MCP connection: + +[source,bash] +---- +claude mcp add --transport http redpanda-aigateway https://gw.ai.panda.com/mcp \ + --header "Authorization: Bearer " \ + --header "rp-aigw-id: " +---- + +Alternatively, edit `~/.claude/config.json`: + +[source,json] +---- +{ + "mcpServers": { + "redpanda-ai-gateway": { + "transport": "http", + "url": "https://gw.ai.panda.com/mcp", + "headers": { + "Authorization": "Bearer ", + "rp-aigw-id": "" + } + } + }, + "apiProviders": { + "redpanda": { + "baseURL": "https://gw.ai.panda.com", + "headers": { + "rp-aigw-id": "" + } + } + } +} +---- + +For detailed Claude Code setup, see xref:ai-agents:ai-gateway/integrations/claude-code-user.adoc[]. + +=== Continue.dev + +Edit your Continue config file (`~/.continue/config.json`): + +[source,json] +---- +{ + "models": [ + { + "title": "Redpanda AI Gateway - GPT-4", + "provider": "openai", + "model": "openai/gpt-4", + "apiBase": "https://gw.ai.panda.com", + "apiKey": "", + "requestOptions": { + "headers": { + "rp-aigw-id": "" + } + } + }, + { + "title": "Redpanda AI Gateway - Claude", + "provider": "anthropic", + "model": "anthropic/claude-3-5-sonnet-20241022", + "apiBase": "https://gw.ai.panda.com", + "apiKey": "", + "requestOptions": { + "headers": { + "rp-aigw-id": "" + } + } + } + ] +} +---- + +For detailed Continue setup, see xref:ai-agents:ai-gateway/integrations/continue-user.adoc[]. + +=== Cursor IDE + +Configure Cursor in Settings (*Cursor* → *Settings* or `Cmd+,`): + +[source,json] +---- +{ + "cursor.ai.providers.openai.apiBase": "https://gw.ai.panda.com", + "cursor.ai.providers.openai.defaultHeaders": { + "rp-aigw-id": "" + } +} +---- + +For detailed Cursor setup, see xref:ai-agents:ai-gateway/integrations/cursor-user.adoc[]. + +=== Custom applications + +For custom applications using OpenAI or Anthropic SDKs: + +*Python with OpenAI SDK*: + +[source,python] +---- +from openai import OpenAI + +client = OpenAI( + base_url="https://gw.ai.panda.com", + api_key="", + default_headers={ + "rp-aigw-id": "" + } +) +---- + +*Python with Anthropic SDK*: + +[source,python] +---- +from anthropic import Anthropic + +client = Anthropic( + base_url="https://gw.ai.panda.com", + api_key="", + default_headers={ + "rp-aigw-id": "" + } +) +---- + +*Node.js with OpenAI SDK*: + +[source,javascript] +---- +import OpenAI from 'openai'; + +const openai = new OpenAI({ + baseURL: 'https://gw.ai.panda.com', + apiKey: process.env.REDPANDA_API_KEY, + defaultHeaders: { + 'rp-aigw-id': '' + } +}); +---- + +== What you've accomplished + +After completing this quickstart, you: + +* ✓ Configured an LLM provider and enabled models +* ✓ Created your first AI Gateway +* ✓ Sent requests through the gateway +* ✓ Verified requests in the observability dashboard +* ✓ (Optional) Configured failover with provider pools +* ✓ (Optional) Created CEL routing rules +* ✓ (Optional) Set up MCP tool aggregation + +== Next steps + +Explore advanced AI Gateway features: + +* xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Advanced CEL routing patterns for traffic distribution and cost optimization +* xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure MCP server aggregation and deferred tool loading +* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor request logs, token usage, and costs +* xref:ai-agents:ai-gateway/migration-guide.adoc[]: Migrate existing LLM integrations to AI Gateway +* xref:ai-agents:ai-gateway/integrations/index.adoc[]: Connect more AI development tools + +Learn about the architecture: + +* xref:ai-agents:ai-gateway/gateway-architecture.adoc[]: Technical architecture, request lifecycle, and deployment models +* xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[]: Problems AI Gateway solves and common use cases diff --git a/modules/ai-agents/pages/ai-gateway/index.adoc b/modules/ai-agents/pages/ai-gateway/index.adoc index 2a3d3ebca..8c685847b 100644 --- a/modules/ai-agents/pages/ai-gateway/index.adoc +++ b/modules/ai-agents/pages/ai-gateway/index.adoc @@ -1,6 +1,8 @@ = AI Gateway :description: Unified access layer for LLM providers and AI tools with centralized routing, policy enforcement, cost management, and observability. :page-layout: index -:personas: platform_admin, app_developer, evaluator +:page-personas: platform_admin, app_developer, evaluator + +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] Redpanda AI Gateway provides a unified access layer for LLM providers and AI tools that sits between your applications and the AI services they use. It delivers centralized routing, policy enforcement, cost management, and observability for all your AI traffic. \ No newline at end of file diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc index f987cf1f0..3647efca4 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc @@ -6,6 +6,8 @@ :learning-objective-2: Set up authentication and access control for Claude Code clients :learning-objective-3: Deploy MCP tool aggregation for Claude Code tool discovery +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + Configure Redpanda AI Gateway to support Claude Code clients accessing LLM providers and MCP tools through a unified endpoint. After reading this page, you will be able to: @@ -19,7 +21,7 @@ After reading this page, you will be able to: * AI Gateway deployed on a BYOC cluster running Redpanda version 25.3 or later * Administrator access to the AI Gateway UI * At least one LLM provider API key (OpenAI or Anthropic) -* Understanding of xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[AI Gateway concepts] +* Understanding of xref:ai-agents:ai-gateway/gateway-architecture.adoc[AI Gateway concepts] == Architecture overview diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc index 3273a29af..8a6dcc85a 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc @@ -6,7 +6,9 @@ :learning-objective-2: Set up MCP server integration through AI Gateway :learning-objective-3: Verify Claude Code is routing requests through the gateway -After xref:ai-agents:ai-gateway/ai-gateway.adoc[configuring your AI Gateway], set up Claude Code to route LLM requests and access MCP tools through the gateway's unified endpoints. +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + +After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gateway], set up Claude Code to route LLM requests and access MCP tools through the gateway's unified endpoints. After reading this page, you will be able to: @@ -20,8 +22,8 @@ Before configuring Claude Code, ensure you have: * Claude Code CLI installed (download from https://github.com/anthropics/claude-code[Anthropic's GitHub^]) * An active Redpanda AI Gateway with: -** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-1-enable-a-provider[Enable a provider]) -** A gateway created and configured (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-3-create-a-gateway[Create a gateway]) +** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-1-enable-a-provider[Enable a provider]) +** A gateway created and configured (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-3-create-a-gateway[Create a gateway]) * Your AI Gateway credentials: ** Gateway endpoint URL (for example, `https://gw.ai.panda.com`) ** Gateway ID (for example, `gateway-abc123`) @@ -480,5 +482,5 @@ chmod 600 ~/.claude/config.json == Related pages -* xref:ai-agents:ai-gateway/ai-gateway.adoc[]: Create and configure your AI Gateway -* xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[]: Learn about AI Gateway architecture and benefits +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[]: Create and configure your AI Gateway +* xref:ai-agents:ai-gateway/gateway-architecture.adoc[]: Learn about AI Gateway architecture and benefits diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc index 46867c534..376927919 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc @@ -6,6 +6,8 @@ :learning-objective-2: Set up authentication and access control for Cline clients :learning-objective-3: Deploy MCP tool aggregation for Cline tool discovery +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + Configure Redpanda AI Gateway to support Cline (formerly Claude Dev) clients accessing LLM providers and MCP tools through a unified endpoint. After reading this page, you will be able to: @@ -19,7 +21,7 @@ After reading this page, you will be able to: * AI Gateway deployed on a BYOC cluster running Redpanda version 25.3 or later * Administrator access to the AI Gateway UI * At least one LLM provider API key (Anthropic or OpenAI) -* Understanding of xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[AI Gateway concepts] +* Understanding of xref:ai-agents:ai-gateway/gateway-architecture.adoc[AI Gateway concepts] == About Cline diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc index dce1e03c3..5e48edb25 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc @@ -6,7 +6,9 @@ :learning-objective-2: Set up autonomous mode with custom instructions and browser integration :learning-objective-3: Verify Cline routes requests through the gateway and optimize for cost -After xref:ai-agents:ai-gateway/ai-gateway.adoc[configuring your AI Gateway], set up Cline (formerly Claude Dev) to route LLM requests and access MCP tools through the gateway's unified endpoints. +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + +After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gateway], set up Cline (formerly Claude Dev) to route LLM requests and access MCP tools through the gateway's unified endpoints. After reading this page, you will be able to: @@ -20,8 +22,8 @@ Before configuring Cline, ensure you have: * Cline VS Code extension installed (search for "Cline" in VS Code Extensions) * An active Redpanda AI Gateway with: -** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-1-enable-a-provider[Enable a provider]) -** A gateway created and configured (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-3-create-a-gateway[Create a gateway]) +** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-1-enable-a-provider[Enable a provider]) +** A gateway created and configured (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-3-create-a-gateway[Create a gateway]) * Your AI Gateway credentials: ** Gateway endpoint URL (for example, `https://gw.ai.panda.com`) ** Gateway ID (for example, `gateway-abc123`) @@ -631,7 +633,7 @@ You may be hitting rate limits. Check the dashboard for rate limit metrics and i . **Provider outage** + -Check the AI Gateway dashboard for provider status. If the primary provider is down, configure failover (see xref:ai-agents:ai-gateway/quickstart-enhanced.adoc#step-6-configure-provider-pool-with-fallback[Configure failover]). +Check the AI Gateway dashboard for provider status. If the primary provider is down, configure failover (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#configure-provider-pool-with-fallback[Configure failover]). === Settings changes not taking effect @@ -753,6 +755,6 @@ The gateway automatically blocks requests that would exceed the limit. == Related pages -* xref:ai-agents:ai-gateway/ai-gateway.adoc[]: Create and configure your AI Gateway -* xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[]: Learn about AI Gateway architecture and benefits +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[]: Create and configure your AI Gateway +* xref:ai-agents:ai-gateway/gateway-architecture.adoc[]: Learn about AI Gateway architecture and benefits * xref:ai-agents:ai-gateway/integrations/claude-code-user.adoc[]: Configure Claude Code with AI Gateway diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index 741e689b1..471128d79 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -6,6 +6,8 @@ :learning-objective-2: Set up multi-provider backends with native format routing :learning-objective-3: Deploy MCP tool aggregation for Continue.dev tool discovery +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + Configure Redpanda AI Gateway to support Continue.dev clients accessing multiple LLM providers and MCP tools through flexible, native-format endpoints. After reading this page, you will be able to: @@ -19,7 +21,7 @@ After reading this page, you will be able to: * AI Gateway deployed on a BYOC cluster running Redpanda version 25.3 or later * Administrator access to the AI Gateway UI * API keys for at least one LLM provider (Anthropic, OpenAI, or others) -* Understanding of xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[AI Gateway concepts] +* Understanding of xref:ai-agents:ai-gateway/gateway-architecture.adoc[AI Gateway concepts] == About Continue.dev diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index 0194c8d3d..dac4827fa 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -6,7 +6,9 @@ :learning-objective-2: Set up MCP server integration through AI Gateway :learning-objective-3: Optimize Continue.dev settings for cost and performance -After xref:ai-agents:ai-gateway/ai-gateway.adoc[configuring your AI Gateway], set up Continue.dev to route LLM requests and access MCP tools through the gateway's unified endpoints. +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + +After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gateway], set up Continue.dev to route LLM requests and access MCP tools through the gateway's unified endpoints. After reading this page, you will be able to: @@ -22,8 +24,8 @@ Before configuring Continue.dev, ensure you have: ** VS Code: Search for "Continue" in Extensions ** JetBrains IDEs: Install from the JetBrains Marketplace * An active Redpanda AI Gateway with: -** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-1-enable-a-provider[Enable a provider]) -** A gateway created and configured (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-3-create-a-gateway[Create a gateway]) +** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-1-enable-a-provider[Enable a provider]) +** A gateway created and configured (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-3-create-a-gateway[Create a gateway]) * Your AI Gateway credentials: ** Gateway endpoint URL (for example, `https://gw.ai.panda.com`) ** Gateway ID (for example, `gateway-abc123`) @@ -940,7 +942,7 @@ Autocomplete rarely needs more than 256 tokens, while chat responses can vary. == Related pages -* xref:ai-agents:ai-gateway/ai-gateway.adoc[]: Create and configure your AI Gateway -* xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[]: Learn about AI Gateway architecture and benefits +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[]: Create and configure your AI Gateway +* xref:ai-agents:ai-gateway/gateway-architecture.adoc[]: Learn about AI Gateway architecture and benefits * xref:ai-agents:ai-gateway/integrations/claude-code-user.adoc[]: Configure Claude Code with AI Gateway * xref:ai-agents:ai-gateway/integrations/cline-user.adoc[]: Configure Cline with AI Gateway diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc index f715d170f..6ad5c0f58 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc @@ -6,6 +6,8 @@ :learning-objective-2: Set up OpenAI-compatible transforms for multi-provider routing :learning-objective-3: Deploy multi-tenant authentication strategies for Cursor clients +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + Configure Redpanda AI Gateway to support Cursor IDE clients accessing multiple LLM providers and MCP tools through OpenAI-compatible endpoints. After reading this page, you will be able to: @@ -19,7 +21,7 @@ After reading this page, you will be able to: * AI Gateway deployed on a BYOC cluster running Redpanda version 25.3 or later * Administrator access to the AI Gateway UI * API keys for at least one LLM provider (Anthropic, OpenAI, or others) -* Understanding of xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[AI Gateway concepts] +* Understanding of xref:ai-agents:ai-gateway/gateway-architecture.adoc[AI Gateway concepts] == About Cursor IDE diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc index 44573a224..5cd74487b 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc @@ -6,7 +6,9 @@ :learning-objective-2: Set up MCP server integration for tool access through the gateway :learning-objective-3: Optimize Cursor settings for multi-tenancy and cost control -After xref:ai-agents:ai-gateway/ai-gateway.adoc[configuring your AI Gateway], set up Cursor IDE to route LLM requests and access MCP tools through the gateway's unified endpoints. +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + +After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gateway], set up Cursor IDE to route LLM requests and access MCP tools through the gateway's unified endpoints. After reading this page, you will be able to: @@ -20,8 +22,8 @@ Before configuring Cursor IDE, ensure you have: * Cursor IDE installed (download from https://cursor.sh[cursor.sh^]) * An active Redpanda AI Gateway with: -** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-1-enable-a-provider[Enable a provider]) -** A gateway created and configured (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-3-create-a-gateway[Create a gateway]) +** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-1-enable-a-provider[Enable a provider]) +** A gateway created and configured (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-3-create-a-gateway[Create a gateway]) * Your AI Gateway credentials: ** Gateway endpoint URL (for example, `https://gw.ai.panda.com`) ** Gateway ID (for example, `gateway-abc123`) @@ -830,8 +832,8 @@ This sends only search + orchestrator tools initially, reducing token usage sign == Related pages -* xref:ai-agents:ai-gateway/ai-gateway.adoc[]: Create and configure your AI Gateway -* xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[]: Learn about AI Gateway architecture and benefits +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[]: Create and configure your AI Gateway +* xref:ai-agents:ai-gateway/gateway-architecture.adoc[]: Learn about AI Gateway architecture and benefits * xref:ai-agents:ai-gateway/integrations/claude-code-user.adoc[]: Configure Claude Code with AI Gateway * xref:ai-agents:ai-gateway/integrations/continue-user.adoc[]: Configure Continue.dev with AI Gateway * xref:ai-agents:ai-gateway/integrations/cline-user.adoc[]: Configure Cline with AI Gateway diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc index ad8aaa1d1..b1e75933d 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc @@ -6,6 +6,8 @@ :learning-objective-2: Deploy multi-tenant authentication strategies for Copilot clients :learning-objective-3: Set up model aliasing and BYOK routing for GitHub Copilot +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + Configure Redpanda AI Gateway to support GitHub Copilot clients accessing multiple LLM providers through OpenAI-compatible endpoints with bring-your-own-key (BYOK) support. After reading this page, you will be able to: @@ -19,7 +21,7 @@ After reading this page, you will be able to: * AI Gateway deployed on a BYOC cluster running Redpanda version 25.3 or later * Administrator access to the AI Gateway UI * API keys for at least one LLM provider (OpenAI, Anthropic, or others) -* Understanding of xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[AI Gateway concepts] +* Understanding of xref:ai-agents:ai-gateway/gateway-architecture.adoc[AI Gateway concepts] * GitHub Copilot Business or Enterprise subscription (for BYOK and custom endpoints) == About GitHub Copilot diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc index d6ad759bf..1896a7544 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc @@ -6,7 +6,9 @@ :learning-objective-2: Set up multi-tenancy with gateway ID headers for cost tracking :learning-objective-3: Configure enterprise BYOK deployments for team-wide Copilot access -After xref:ai-agents:ai-gateway/ai-gateway.adoc[configuring your AI Gateway], set up GitHub Copilot to route LLM requests through the gateway for centralized observability, cost management, and provider flexibility. +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] + +After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gateway], set up GitHub Copilot to route LLM requests through the gateway for centralized observability, cost management, and provider flexibility. After reading this page, you will be able to: @@ -20,8 +22,8 @@ Before configuring GitHub Copilot, ensure you have: * GitHub Copilot subscription (Individual, Business, or Enterprise) * An active Redpanda AI Gateway with: -** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-1-enable-a-provider[Enable a provider]) -** A gateway created and configured (see xref:ai-agents:ai-gateway/ai-gateway.adoc#step-3-create-a-gateway[Create a gateway]) +** At least one LLM provider enabled (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-1-enable-a-provider[Enable a provider]) +** A gateway created and configured (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc#step-3-create-a-gateway[Create a gateway]) * Your AI Gateway credentials: ** Gateway endpoint URL (for example, `https://gw.ai.panda.com`) ** Gateway ID (for example, `gateway-abc123`) @@ -1003,8 +1005,8 @@ Generate project-specific cost reports from the gateway dashboard. == Related pages -* xref:ai-agents:ai-gateway/ai-gateway.adoc[]: Create and configure your AI Gateway -* xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[]: Learn about AI Gateway architecture and benefits +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[]: Create and configure your AI Gateway +* xref:ai-agents:ai-gateway/gateway-architecture.adoc[]: Learn about AI Gateway architecture and benefits * xref:ai-agents:ai-gateway/integrations/claude-code-user.adoc[]: Configure Claude Code with AI Gateway * xref:ai-agents:ai-gateway/integrations/continue-user.adoc[]: Configure Continue.dev with AI Gateway * xref:ai-agents:ai-gateway/integrations/cursor-user.adoc[]: Configure Cursor IDE with AI Gateway diff --git a/modules/ai-agents/pages/ai-gateway/integrations/index.adoc b/modules/ai-agents/pages/ai-gateway/integrations/index.adoc index f899d2aca..bf8c6966c 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/index.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/index.adoc @@ -1,3 +1,5 @@ = AI Gateway Integrations :description: Configure AI development tools and IDEs to connect to Redpanda AI Gateway for centralized LLM routing and MCP tool aggregation. :page-layout: index + +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] diff --git a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc index cb3cbd058..5ed21f841 100644 --- a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc @@ -1,14 +1,14 @@ = MCP Aggregation and Orchestration Guide :description: Guide to MCP aggregation and orchestration in Redpanda AI Gateway, including architecture, deferred tool loading, orchestrator workflows, administration, observability, security, and integration examples. -:page-personas: app_developer +:page-topic-type: guide +:page-personas: app_developer, platform_admin +:learning-objective-1: Configure MCP aggregation with deferred tool loading to reduce token costs +:learning-objective-2: Write orchestrator workflows to reduce multi-step interactions +:learning-objective-3: Manage approved MCP servers with security controls and audit trails -AI Gateway provides MCP (Model Context Protocol) aggregation, allowing AI agents to access tools from multiple MCP servers through a single unified endpoint. This eliminates the need for agents to manage multiple MCP connections and significantly reduces token costs through deferred tool loading. - -After reading this page, you will be able to: +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] -* Configure MCP aggregation with deferred tool loading to reduce token costs by 80-90%. -* Write orchestrator workflows in JavaScript to reduce multi-step interactions from multiple round trips to a single request. -* Add and manage approved MCP servers with appropriate security controls and audit trails. +AI Gateway provides MCP (Model Context Protocol) aggregation, allowing AI agents to access tools from multiple MCP servers through a single unified endpoint. This eliminates the need for agents to manage multiple MCP connections and significantly reduces token costs through deferred tool loading. MCP aggregation benefits: diff --git a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc index 3ebd7bb98..bb58ba25c 100644 --- a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc @@ -1,6 +1,12 @@ = Migrate to AI Gateway :description: Step-by-step migration guide to transition existing applications from direct LLM provider integrations to Redpanda AI Gateway with minimal disruption. -:page-personas: app_developer +:page-topic-type: how-to +:page-personas: app_developer, platform_admin +:learning-objective-1: Migrate LLM integrations to AI Gateway with zero downtime using feature flags +:learning-objective-2: Verify gateway connectivity and compare performance metrics +:learning-objective-3: Roll back to direct integration if issues arise during migration + +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] This guide helps you migrate existing applications from direct LLM provider integrations (OpenAI, Anthropic, and others) to Redpanda AI Gateway. Design the migration to be incremental and reversible, allowing you to test thoroughly before fully committing. @@ -8,12 +14,6 @@ This guide helps you migrate existing applications from direct LLM provider inte **Rollback difficulty:** Easy (feature flag or environment variable) -After completing this migration, you will be able to: - -* Migrate existing LLM integrations to AI Gateway with zero downtime using feature flags and parallel operation. -* Verify gateway connectivity and compare performance metrics between direct and gateway-routed requests. -* Roll back to direct integration immediately if issues arise during migration. - == Prerequisites Before migrating, ensure you have: diff --git a/modules/ai-agents/pages/ai-gateway/observability-logs.adoc b/modules/ai-agents/pages/ai-gateway/observability-logs.adoc index ff82edbb8..01284d90f 100644 --- a/modules/ai-agents/pages/ai-gateway/observability-logs.adoc +++ b/modules/ai-agents/pages/ai-gateway/observability-logs.adoc @@ -1,14 +1,14 @@ = Observability: Logs :description: Guide to AI Gateway request logs, including where to find logs, log fields, filtering, searching, inspecting requests, common analysis tasks, log retention, export options, privacy/security, and troubleshooting. +:page-topic-type: reference :page-personas: platform_admin, app_developer +:learning-objective-1: Locate and filter request logs to debug failures or reconstruct conversations +:learning-objective-2: Interpret log fields to diagnose performance and cost issues +:learning-objective-3: Export logs for compliance auditing or long-term analysis -AI Gateway logs every LLM request that passes through it, capturing the full request/response history, token usage, cost, latency, and routing decisions. This page explains how to find, filter, and interpret request logs. - -After reading this page, you will be able to: +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] -* Locate and filter request logs to debug specific failed requests or reconstruct user conversations. -* Interpret log fields (status codes, token usage, routing decisions) to diagnose performance and cost issues. -* Export logs for compliance auditing or long-term analysis. +AI Gateway logs every LLM request that passes through it, capturing the full request/response history, token usage, cost, latency, and routing decisions. This page explains how to find, filter, and interpret request logs. == Before you begin diff --git a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc b/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc index 0dc06bc3e..31dea7fe8 100644 --- a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc +++ b/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc @@ -1,14 +1,14 @@ = Observability: Metrics and Analytics :description: Guide to AI Gateway metrics and analytics, including where to find metrics, key metrics explained, dashboard views, filtering/grouping, alerting, exporting, common analysis tasks, retention, API access, best practices, and troubleshooting. -:page-personas: platform_admin +:page-topic-type: reference +:page-personas: platform_admin, app_developer +:learning-objective-1: Monitor aggregate metrics to track usage patterns and budget adherence +:learning-objective-2: Compare model and provider performance using latency and cost metrics +:learning-objective-3: Configure alerts for budget thresholds and performance degradation -AI Gateway provides aggregate metrics and analytics dashboards to help you understand usage patterns, costs, performance, and errors across all your LLM traffic. - -After reading this page, you will be able to: +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] -* Monitor aggregate metrics (request volume, token usage, estimated spend) to track usage patterns and budget adherence. -* Compare model and provider performance using latency, error rate, and cost metrics. -* Configure alerts for budget thresholds and performance degradation. +AI Gateway provides aggregate metrics and analytics dashboards to help you understand usage patterns, costs, performance, and errors across all your LLM traffic. == Before you begin @@ -859,7 +859,7 @@ Possible causes: Solution: 1. Remove filters, widen time range -2. Send test request (see xref:ai-agents:ai-gateway/quickstart-enhanced.adoc[]) +2. Send test request (see xref:ai-agents:ai-gateway/gateway-quickstart.adoc[]) 3. Check permissions with admin == Next steps diff --git a/modules/ai-agents/pages/ai-gateway/quickstart-enhanced.adoc b/modules/ai-agents/pages/ai-gateway/quickstart-enhanced.adoc deleted file mode 100644 index 6298e7ec3..000000000 --- a/modules/ai-agents/pages/ai-gateway/quickstart-enhanced.adoc +++ /dev/null @@ -1,453 +0,0 @@ -= DRAFT Quickstart enhanced -:description: Get started with AI Gateway by routing your first request, viewing observability data, testing failover and CEL routing. -:page-personas: app_developer - -Get your first request routed through Redpanda AI Gateway. - -After completing this quickstart, you will be able to: - -* Route your first LLM request through AI Gateway using the Cloud UI and verify it in the observability dashboard. -* Configure a provider and gateway with correct authentication and routing policies. -* Test failover behavior and CEL routing rules in a development environment. - -== Prerequisites - -Before starting, ensure you have: - -* Redpanda Cloud account with BYOC -* Admin access to configure providers and gateways -* API keys for at least one LLM provider (OpenAI, Anthropic, etc.) -* Python 3.8+ or Node.js 18+ (for examples) - -== Step 1: Configure a provider - -Providers must be configured before they can be used in gateways. - -// PLACEHOLDER: Add UI navigation path, e.g., "Console → AI Gateway → Providers → Add Provider" - -1. Navigate to *Providers*: - * Open Redpanda Cloud Console - * Go to // PLACEHOLDER: exact menu path - -2. Add provider: -+ ----- -Provider: OpenAI -API Key: sk-... -Enabled Models: gpt-4o, gpt-4o-mini ----- - - // PLACEHOLDER: Add screenshot of provider configuration form - -3. Verify: - - * Provider status shows "Active" - * Models appear in model catalog - -Alternative: CLI (if available) - -[source,bash] ----- -# PLACEHOLDER: CLI command for adding provider -rpk cloud ai-gateway provider create \ - --provider openai \ - --api-key sk-... \ - --models gpt-4o,gpt-4o-mini ----- - - -AI Gateway supports the following LLM providers: - -* OpenAI -* Anthropic -// PLACEHOLDER: Add other supported providers - -== Step 2: Create a gateway - -Gateways define routing policies, rate limits, and observability scope. - -// PLACEHOLDER: Add UI navigation path - -1. Navigate to *Gateways*: - * Go to // PLACEHOLDER: exact menu path - -2. Create gateway: -+ ----- -Name: my-first-gateway -Workspace: default -Description: Quickstart gateway for testing ----- - - // PLACEHOLDER: Add screenshot of gateway creation form - -3. Save gateway ID: -+ -After creation, copy your gateway ID (required for requests): -+ ----- -Gateway ID: gw_abc123... -Gateway Endpoint: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1 ----- - - // PLACEHOLDER: Confirm exact endpoint format - -When planning your gateway structure, consider these common patterns: - -* One gateway per environment (staging, production) -* One gateway per team (for budget isolation) -* One gateway per customer (for multi-tenant SaaS) - -== Step 3: Send your first request - -Route a request through your gateway. - -[tabs] -==== -Python:: -+ --- -[source,python] ----- -from openai import OpenAI -import os - -# Configure client to use AI Gateway -client = OpenAI( - base_url="https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1", # Gateway endpoint - api_key=os.getenv("REDPANDA_CLOUD_TOKEN"), # Your Redpanda Cloud token - default_headers={ - "rp-aigw-id": "gw_abc123..." # Your gateway ID from Step 2 - } -) - -# Make a request (note the vendor/model_id format) -response = client.chat.completions.create( - model="openai/gpt-4o-mini", # Format: {provider}/{model} - messages=[ - {"role": "user", "content": "Say 'Hello from AI Gateway!'"} - ], - max_tokens=20 -) - -print(response.choices[0].message.content) -# Output: Hello from AI Gateway! ----- --- - -TypeScript/JavaScript:: -+ --- -[source,typescript] ----- -import OpenAI from 'openai'; - -const client = new OpenAI({ - baseURL: 'https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1', - apiKey: process.env.REDPANDA_CLOUD_TOKEN, - defaultHeaders: { - 'rp-aigw-id': 'gw_abc123...' - } -}); - -const response = await client.chat.completions.create({ - model: 'openai/gpt-4o-mini', - messages: [ - { role: 'user', content: 'Say "Hello from AI Gateway!"' } - ], - max_tokens: 20 -}); - -console.log(response.choices[0].message.content); -// Output: Hello from AI Gateway! ----- --- - -cURL:: -+ --- -[source,bash] ----- -curl https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ - -H "rp-aigw-id: gw_abc123..." \ - -d '{ - "model": "openai/gpt-4o-mini", - "messages": [ - {"role": "user", "content": "Say \"Hello from AI Gateway!\""} - ], - "max_tokens": 20 - }' ----- - -Expected response: - -[source,json] ----- -{ - "id": "chatcmpl-...", - "object": "chat.completion", - "created": 1704844800, - "model": "openai/gpt-4o-mini", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "Hello from AI Gateway!" - }, - "finish_reason": "stop" - } - ], - "usage": { - "prompt_tokens": 8, - "completion_tokens": 5, - "total_tokens": 13 - } -} ----- --- -==== - - -If your request fails, check these common issues: - -* `401 Unauthorized` - Verify your `REDPANDA_CLOUD_TOKEN` is valid -* `404 Not Found` - Confirm the `base_url` matches your gateway endpoint -* `Model not found` - Ensure the model is enabled in Step 1 -* `Missing rp-aigw-id` - Add the gateway ID header to your request - -== Step 4: Verify in observability dashboard - -Confirm your request appears in the AI Gateway dashboard. - -// PLACEHOLDER: Add UI navigation path and screenshots - -1. *Navigate to Logs*: - * Go to // PLACEHOLDER: Console → AI Gateway → {Gateway Name} → Logs - -2. *Find your request*: - * Filter by Gateway: `my-first-gateway` - * Filter by Model: `openai/gpt-4o-mini` - * Time range: Last 5 minutes - -3. *Verify fields*: - * Model: `openai/gpt-4o-mini` - * Provider: OpenAI - * Status: 200 - * Prompt tokens: ~8 - * Completion tokens: ~5 - * Estimated cost: // PLACEHOLDER: $X.XXXX - * Latency: // PLACEHOLDER: ~XXXms - -4. *Click Request to Expand*: - * View full prompt and response - * See request headers - * Check routing decision (which provider pool was used) - -If your request doesn't appear in the logs, try these steps: - -* Wait a few seconds for logs to populate (there may be a brief delay) -* Verify the gateway ID in your request matches the gateway you're viewing -* Check that your client received a successful response (no errors) - -== Next steps: Add failover (optional) - -Add automatic failover to a backup provider for reliability. - -=== Step 5: Add second provider - -Add Anthropic as a fallback option: - -// PLACEHOLDER: Add UI path - -1. *Navigate to Providers* → *Add Provider*: -+ ----- -Provider: Anthropic -API Key: sk-ant-... -Enabled Models: claude-sonnet-3.5 ----- - -2. *Verify*: - - * Anthropic provider status: Active - * Models appear in catalog - -=== Step 6: Configure provider pool with fallback - -Update your gateway to use OpenAI as primary, Anthropic as fallback. - -// PLACEHOLDER: Add UI path and configuration format - -1. *Navigate to Gateway Settings*: - - * Go to // PLACEHOLDER: AI Gateway → {Gateway Name} → Routing - -2. *Configure provider pool*: -+ -[source,yaml] ----- -# PLACEHOLDER: Confirm actual configuration format -routing: - primary_pool: - * provider: openai - models: [gpt-4o, gpt-4o-mini] - fallback_pool: - * provider: anthropic - models: [claude-sonnet-3.5] - -fallback_triggers: - * rate_limit_exceeded - * timeout - * 5xx_errors ----- - - // PLACEHOLDER: Add screenshot of routing configuration - -3. *Save configuration* - -=== Step 7: Test failover - -Simulate a provider failure to see fallback in action. - -// PLACEHOLDER: Add method to test failback, or skip if not easily testable - -*Option A: Disable primary provider temporarily* - -1. Disable OpenAI provider in settings -2. Send request with `openai/gpt-4o` model -3. Gateway should automatically route to Anthropic fallback -4. Check logs to confirm fallback was used - -*Option B: Trigger rate limit* - -1. Send many requests rapidly to hit rate limit -2. Gateway should fallback to Anthropic -3. Check logs for "fallback_triggered" indicator - -Verify fallback: - -[source,python] ----- -response = client.chat.completions.create( - model="openai/gpt-4o", # Request OpenAI model - messages=[{"role": "user", "content": "Test fallback"}] -) - -# Check which provider actually handled it -# PLACEHOLDER: How to verify this - response header? Log metadata? ----- - - -Verify the fallback in the observability dashboard. The request log should display: - -* Requested model: `openai/gpt-4o` -* Actual provider: Anthropic (fallback) -* Fallback reason: rate_limit, timeout, or error - - -== Next steps: Add routing rule (optional) - -Use CEL expressions to route requests based on headers or content. - -=== Step 8: Create CEL routing rule - -Route premium users to better models automatically. - -// PLACEHOLDER: Add UI path for CEL configuration - -1. Navigate to *Gateway Settings*: - - * Go to // PLACEHOLDER: AI Gateway → {Gateway Name} → Routing Rules - -2. Add CEL rule: -+ -[source,cel] ----- -# Route based on user tier header -request.headers["x-user-tier"] == "premium" - ? "openai/gpt-4o" - : "openai/gpt-4o-mini" ----- - - // PLACEHOLDER: Add screenshot of CEL editor with syntax highlighting - -3. Test rule (if UI supports testing): - - * Input test headers: `x-user-tier: premium` - * Verify output: `openai/gpt-4o` - * Input test headers: `x-user-tier: free` - * Verify output: `openai/gpt-4o-mini` - -4. Save rule - -=== Step 9: Test routing rule - -Send requests with different headers and verify routing. - -*Premium user request*: - -[source,python] ----- -response = client.chat.completions.create( - model="auto", # PLACEHOLDER: or how to trigger CEL routing - messages=[{"role": "user", "content": "Hello"}], - extra_headers={"x-user-tier": "premium"} -) - -# Should route to gpt-4o (premium model) ----- - - -*Free user request*: - -[source,python] ----- -response = client.chat.completions.create( - model="auto", - messages=[{"role": "user", "content": "Hello"}], - extra_headers={"x-user-tier": "free"} -) - -# Should route to gpt-4o-mini (cost-effective model) ----- - - -To verify the routing rule worked, check the observability dashboard: - -* Open the request logs for your gateway -* Confirm the correct model was selected based on the header value -* Review the routing decision explanation to see which CEL rule matched - -== What's next? - -After completing this quickstart, consider these configurations to optimize your gateway: - -. *Set rate limits* to protect against runaway costs and prevent abuse. -. *Add spend limits* to set monthly budgets per gateway and receive alerts before limits are reached. -. *Configure MCP aggregation* to give agents access to tools and reduce token costs with deferred loading. - -// Explore advanced features -// A/B testing models -// Multi-tenancy patterns -// Cost optimization -// Performance tuning - -Connect your favorite AI development tools to AI Gateway: - -* xref:ai-agents:ai-gateway/integrations/index.adoc[]: Overview of all integrations -* xref:ai-agents:ai-gateway/integrations/claude-code-user.adoc[]: Claude Code -* xref:ai-agents:ai-gateway/integrations/cline-user.adoc[]: Cline -* xref:ai-agents:ai-gateway/integrations/continue-user.adoc[]: Continue.dev -* xref:ai-agents:ai-gateway/integrations/cursor-user.adoc[]: Cursor IDE -* xref:ai-agents:ai-gateway/integrations/github-copilot-user.adoc[]: GitHub Copilot - - -== Related pages - -* xref:ai-agents:ai-gateway/ai-gateway-overview.adoc[] -* xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[] -* xref:ai-agents:ai-gateway/observability-logs.adoc[] -* xref:ai-agents:ai-gateway/migration-guide.adoc[] diff --git a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc index d884f4a59..da31e4716 100644 --- a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc @@ -1,18 +1,15 @@ = What is an AI Gateway? :description: Understand what an AI Gateway is, the problems it solves, and how it benefits your AI infrastructure. :page-topic-type: concept -:personas: app_developer, platform_admin +:page-personas: app_developer, platform_admin +:learning-objective-1: Describe how AI Gateway centralizes LLM provider management and reduces operational complexity +:learning-objective-2: Identify key features that address common LLM integration challenges +:learning-objective-3: Determine whether AI Gateway fits your use case based on traffic volume and provider diversity -NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. +include::ai-agents:partial$ai-gateway-byoc-note.adoc[] Redpanda AI Gateway is a unified access layer for LLM providers and AI tools that sits between your applications and the AI services they use. It provides centralized routing, policy enforcement, cost management, and observability for all your AI traffic. -After reading this page, you will be able to: - -* Describe how AI Gateway centralizes LLM provider management and reduces operational complexity -* Identify key features (routing, observability, cost controls) that address common LLM integration challenges -* Determine whether AI Gateway fits your use case based on traffic volume and provider diversity - == The problem Modern AI applications face four critical challenges that increase costs, reduce reliability, and slow down development. @@ -174,7 +171,7 @@ Now that you understand what AI Gateway is and how it can benefit your organizat *For Administrators:* * xref:ai-gateway/admin/setup-guide.adoc[Setup Guide] - Enable providers, models, and create gateways -* xref:ai-gateway/ai-gateway-overview.adoc[Architecture Deep Dive] - Technical architecture details +* xref:ai-gateway/gateway-architecture.adoc[Architecture Deep Dive] - Technical architecture details *For Builders:* diff --git a/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md b/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md index 3811c26b5..bc62f7e7c 100644 --- a/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md +++ b/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md @@ -79,8 +79,8 @@ The current AI Gateway documentation is comprehensive but doesn't clearly distin | Access Management page | Admin | MEDIUM | Missing | ### Existing Content Gaps -1. **ai-gateway-overview.adoc** - Too dense, mixes Admin and Builder concerns -2. **ai-gateway.adoc (quickstart)** - Conflates Admin setup with Builder usage +1. **gateway-architecture.adoc** - Too dense, mixes Admin and Builder concerns +2. **gateway-quickstart.adoc (quickstart)** - Conflates Admin setup with Builder usage 3. **index.adoc** - Too minimal, provides no guidance 4. **No discovery mechanism** - Builders don't know which gateways they can use @@ -129,13 +129,13 @@ AI Gateway/ │ └── ... │ ├── Reference/ -│ ├── ai-gateway-overview.adoc (Refactored: Technical deep-dive) +│ ├── gateway-architecture.adoc (Refactored: Technical deep-dive) │ ├── cel-routing-cookbook.adoc (Existing) │ ├── mcp-aggregation-guide.adoc (Existing) │ ├── observability-logs.adoc (Existing) │ ├── observability-metrics.adoc (Existing) │ ├── migration-guide.adoc (Existing) -│ └── quickstart-enhanced.adoc (Existing or remove if redundant) +│ └── gateway-quickstart.adoc (Consolidated from ai-gateway.adoc and quickstart-enhanced.adoc) ``` --- @@ -176,7 +176,7 @@ You're building AI agents or applications and need to connect to available gatew == Learn More * xref:ai-gateway/what-is-ai-gateway.adoc[What is an AI Gateway?] -* xref:ai-gateway/reference/ai-gateway-overview.adoc[Technical Architecture] +* xref:ai-gateway/reference/gateway-architecture.adoc[Technical Architecture] ``` **Persona Tagging:** Both @@ -186,7 +186,7 @@ You're building AI agents or applications and need to connect to available gatew ### 2. Create what-is-ai-gateway.adoc (HIGH PRIORITY) **Purpose:** Standalone conceptual page answering "What is an AI gateway?" -**Source:** Extract from ai-gateway-overview.adoc (lines 15-147) +**Source:** Extract from gateway-architecture.adoc (lines 15-147) **Content to Include:** - The problem AI Gateway solves @@ -266,7 +266,7 @@ Expected response: List of available models --- -### 4. Refactor ai-gateway.adoc (quickstart) +### 4. Refactor gateway-quickstart.adoc (quickstart) **Current Problem:** Mixes Admin setup (Steps 1-3) with Builder usage (Steps 4-5, integrations) @@ -368,7 +368,7 @@ Inbound connections: ### 7. Update Existing Files -#### ai-gateway-overview.adoc +#### gateway-architecture.adoc **Changes:** - Remove conceptual "What is" content (move to what-is-ai-gateway.adoc) - Focus on technical architecture deep-dive @@ -439,7 +439,7 @@ Inbound connections: *** xref:ai-agents:ai-gateway/builders/monitor-your-usage.adoc[Monitor Your Usage] *** xref:ai-agents:ai-gateway/builders/integrations/index.adoc[Integrations (Builder)] ** Reference -*** xref:ai-agents:ai-gateway/reference/ai-gateway-overview.adoc[Architecture Deep Dive] +*** xref:ai-agents:ai-gateway/reference/gateway-architecture.adoc[Architecture Deep Dive] *** xref:ai-agents:ai-gateway/reference/cel-routing-cookbook.adoc[CEL Routing Cookbook] *** xref:ai-agents:ai-gateway/reference/mcp-aggregation-guide.adoc[MCP Aggregation Guide] *** xref:ai-agents:ai-gateway/reference/observability-logs.adoc[Request Logs] @@ -464,7 +464,7 @@ Inbound connections: 5. Update observability pages with persona distinctions (MEDIUM) ### Phase 3: Polish and Optimize -1. Refactor ai-gateway-overview.adoc (MEDIUM) +1. Refactor gateway-architecture.adoc (MEDIUM) 2. Update mcp-aggregation-guide.adoc with Builder sections (LOW) 3. Create admin/builder overview pages (LOW) 4. Reorganize integrations folders (LOW) @@ -555,14 +555,14 @@ After implementation, evaluate: - `ai-gateway/builders/monitor-your-usage.adoc` ### Files to Move -- `ai-gateway/ai-gateway-overview.adoc` → `ai-gateway/reference/ai-gateway-overview.adoc` +- `ai-gateway/gateway-architecture.adoc` → `ai-gateway/reference/gateway-architecture.adoc` - `ai-gateway/cel-routing-cookbook.adoc` → `ai-gateway/reference/cel-routing-cookbook.adoc` - `ai-gateway/mcp-aggregation-guide.adoc` → `ai-gateway/reference/mcp-aggregation-guide.adoc` - `ai-gateway/observability-*.adoc` → `ai-gateway/reference/observability-*.adoc` ### Files to Refactor -- `ai-gateway/ai-gateway.adoc` (quickstart) - split content between admin and builder paths -- `ai-gateway/ai-gateway-overview.adoc` - extract conceptual content to what-is page +- `ai-gateway/gateway-quickstart.adoc` (quickstart) - split content between admin and builder paths +- `ai-gateway/gateway-architecture.adoc` - extract conceptual content to what-is page - `ai-gateway/observability-logs.adoc` - add persona-specific sections - `ai-gateway/observability-metrics.adoc` - add builder usage section @@ -570,4 +570,4 @@ After implementation, evaluate: - `ai-gateway/integrations/*-admin.adoc` - `ai-gateway/integrations/*-user.adoc` - `ai-gateway/migration-guide.adoc` -- `ai-gateway/quickstart-enhanced.adoc` (review if still needed) +- `ai-gateway/gateway-quickstart.adoc` (consolidated) diff --git a/modules/ai-agents/partials/ai-gateway-byoc-note.adoc b/modules/ai-agents/partials/ai-gateway-byoc-note.adoc new file mode 100644 index 000000000..5d3b58cf0 --- /dev/null +++ b/modules/ai-agents/partials/ai-gateway-byoc-note.adoc @@ -0,0 +1 @@ +NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. From 02bb472b3f8517f266982444688a122c003ca9f6 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 16:32:30 -0700 Subject: [PATCH 28/97] style edits --- .../pages/ai-gateway/admin/setup-guide.adoc | 10 ++--- .../ai-gateway/cel-routing-cookbook.adoc | 31 +++++++------- .../ai-gateway/gateway-architecture.adoc | 26 ++++-------- .../pages/ai-gateway/gateway-quickstart.adoc | 30 ++++---------- .../ai-gateway/mcp-aggregation-guide.adoc | 6 +-- .../pages/ai-gateway/migration-guide.adoc | 41 +++++++++---------- .../ai-gateway/observability-metrics.adoc | 10 ++--- 7 files changed, 66 insertions(+), 88 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc index e3dbdee87..53363e4b1 100644 --- a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc @@ -19,7 +19,7 @@ After completing this guide, you will be able to: * API keys for at least one LLM provider (OpenAI or Anthropic) * (Optional) MCP server endpoints if you plan to use tool aggregation -== Step 1: Enable a provider +== Enable a provider Providers represent upstream services (Anthropic, OpenAI) and associated credentials. Providers are disabled by default and must be enabled explicitly by an administrator. @@ -34,7 +34,7 @@ TIP: Store provider API keys securely. Each provider configuration can have mult Repeat this process for each LLM provider you want to make available through AI Gateway. -== Step 2: Enable models +== Enable models The model catalog is the set of models made available through the gateway. Models are disabled by default. After enabling a provider, you can enable its models. @@ -67,7 +67,7 @@ Examples: * `anthropic/claude-sonnet-3.5` * `openai/gpt-4o-mini` -== Step 3: Create a gateway +== Create a gateway A gateway is a logical configuration boundary (policies + routing + observability) on top of a single deployment. It's a "virtual gateway" that you can create per team, environment (staging/production), product, or customer. @@ -96,7 +96,7 @@ TIP: A workspace is conceptually similar to a resource group in Redpanda streami You'll share the Gateway ID and Endpoint with users who need to access this gateway. -== Step 4: Configure LLM routing +== Configure LLM routing On the gateway details page, select the *LLM* tab to configure rate limits, spend limits, routing, and provider pools with fallback options. @@ -196,7 +196,7 @@ If a provider pool contains multiple providers, you can distribute traffic to ba * *Least latency*: Route to fastest provider based on recent performance * *Cost-optimized*: Route to cheapest provider for each model -== Step 5: Configure MCP tools (optional) +== Configure MCP tools (optional) If your users will build AI agents that need access to tools via MCP (Model Context Protocol), configure MCP tool aggregation. diff --git a/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc index 3d2ee3b0b..352152293 100644 --- a/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc +++ b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc @@ -179,7 +179,7 @@ Each pattern follows this structure: * Verify: How to test * Cost/performance impact: Implications -=== Pattern 1: Tier-based routing +=== Tier-based routing When to use: Different user tiers (free, pro, enterprise) should get different model quality @@ -229,7 +229,7 @@ Cost impact: Use case: SaaS product with tiered pricing where model quality is a differentiator -=== Pattern 2: Environment-based routing +=== Environment-based routing When to use: Prevent staging from using expensive models @@ -272,9 +272,8 @@ Cost impact: Use case: Protect against runaway staging costs -''' -=== Pattern 3: Content-length guard rails +=== Content-length guard rails When to use: Block or downgrade long prompts to prevent cost spikes @@ -336,7 +335,7 @@ Cost impact: Use case: Staging cost controls, prevent prompt injection attacks that inflate token usage -=== Pattern 4: Topic-based routing +=== Topic-based routing When to use: Route different question types to specialized models @@ -385,7 +384,7 @@ Cost impact: Use case: Multi-purpose chatbot with both coding and general queries -=== Pattern 5: Geographic/regional routing +=== Geographic/regional routing When to use: Route by user region for compliance or latency optimization @@ -422,7 +421,7 @@ Cost impact: Neutral (same model, different region) Use case: GDPR compliance, data residency requirements -=== Pattern 6: Customer-specific routing +=== Customer-specific routing When to use: Different customers have different model access (enterprise features) @@ -462,7 +461,7 @@ Cost impact: Use case: Enterprise contracts with premium model access -=== Pattern 7: a/b testing (percentage-based routing) +=== A/B testing (percentage-based routing) When to use: Test new models with a percentage of traffic @@ -517,7 +516,7 @@ Cost impact: Use case: Evaluate new models in production with real traffic -=== Pattern 8: Complexity-based routing +=== Complexity-based routing When to use: Route simple queries to cheap models, complex queries to expensive models @@ -568,7 +567,7 @@ Cost impact: Use case: FAQ chatbot with mix of simple lookups and complex questions -=== Pattern 9: Time-based routing +=== Time-based routing When to use: Use cheaper models during off-peak hours @@ -598,7 +597,7 @@ Cost impact: Use case: Consumer apps with time-zone-specific usage patterns -=== Pattern 10: Fallback chain (multi-level) +=== Fallback chain (multi-level) When to use: Complex fallback logic beyond simple primary/secondary @@ -629,7 +628,7 @@ Use case: Production systems with SLA requirements == Advanced CEL patterns -=== Pattern: Default values with `has()` +=== Default values with `has()` Problem: Field might not exist in request @@ -645,7 +644,7 @@ has(request.body.max_tokens) && request.body.max_tokens > 2000 What happens: Safely checks if `max_tokens` exists before comparing -=== Pattern: Multiple conditions with parentheses +=== Multiple conditions with parentheses Expression: @@ -661,7 +660,7 @@ request.headers["x-environment"] == "production" What happens: Premium users OR VIP customer, AND production → GPT-4o -=== Pattern: Regex matching +=== Regex matching Expression: @@ -675,7 +674,7 @@ request.body.messages[0].content.matches("(?i)(urgent|asap|emergency)") What happens: Messages containing "urgent", "ASAP", or "emergency" (case-insensitive) → GPT-4o -=== Pattern: String array contains +=== String array contains Expression: @@ -689,7 +688,7 @@ Expression: What happens: Only specific customers get premium model -=== Pattern: Reject invalid requests +=== Reject invalid requests Expression: diff --git a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc index 99c869948..916b61691 100644 --- a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc +++ b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc @@ -8,7 +8,7 @@ include::ai-agents:partial$ai-gateway-byoc-note.adoc[] -This page provides technical details about AI Gateway's architecture, request processing, and capabilities. For an introduction to AI Gateway and the problems it solves, see xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[]. +This page provides technical details about AI Gateway's architecture, request processing, and capabilities. For an introduction to AI Gateway and the problems it solves, see xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[] == Architecture overview @@ -134,22 +134,14 @@ The gateway only loads and exposes specific tools when requested, which dramatic == Current limitations -// PLACEHOLDER: List current limitations, for example: -// - Custom model deployments (Azure OpenAI BYOK, AWS Bedrock custom models) -// - Response caching -// - Prompt templates/versioning -// - Guardrails (PII detection, content moderation) -// - Multi-region active-active deployment -// - Metrics export to external systems -// - Budget alerts/notifications - -== Deployment models - -// PLACEHOLDER: Add deployment model details: -// - BYOC deployment requirements -// - Scaling characteristics -// - High availability configuration -// - Regional deployment options +* // PLACEHOLDER: List current limitations, for example: +** // - Custom model deployments (Azure OpenAI BYOK, AWS Bedrock custom models) +** // - Response caching +** // - Prompt templates/versioning +** // - Guardrails (PII detection, content moderation) +** // - Multi-region active-active deployment +** // - Metrics export to external systems +** // - Budget alerts/notifications == Next steps diff --git a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc index dd1505fe1..7c3eadb7c 100644 --- a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc +++ b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc @@ -19,7 +19,7 @@ Before starting, ensure you have: * API key for at least one LLM provider (OpenAI or Anthropic) * Python 3.8+, Node.js 18+, or cURL (for testing) -== Step 1: Configure a provider +== Configure a provider Providers represent upstream LLM services (OpenAI, Anthropic) and their associated credentials. Providers are disabled by default and must be enabled explicitly. @@ -33,7 +33,7 @@ AI Gateway currently supports: * OpenAI * Anthropic -== Step 2: Enable models +== Enable models After enabling a provider, enable the specific models you want to make available through your gateways. @@ -52,7 +52,7 @@ Requests through AI Gateway must use the `vendor/model_id` format. For example: This format allows the gateway to route requests to the correct provider. -== Step 3: Create a gateway +== Create a gateway A gateway is a logical configuration boundary that defines routing policies, rate limits, spend limits, and observability scope. You can create separate gateways per team, environment (staging/production), or customer. @@ -78,7 +78,7 @@ Common gateway patterns: * *Team isolation*: One gateway per team for budget tracking * *Customer multi-tenancy*: One gateway per customer for isolated policies -== Step 4: Send your first request +== Send your first request Now that you've configured a provider and created a gateway, send a test request to verify everything works. @@ -198,7 +198,7 @@ If your request fails, check these common issues: * *Model not found*: Ensure the model is enabled in Step 2 * *Missing rp-aigw-id*: Add the gateway ID header to your request -== Step 5: Verify in observability dashboard +== Verify in observability dashboard Confirm your request appears in the AI Gateway observability dashboard. @@ -233,7 +233,7 @@ If your request doesn't appear: * Verify the gateway ID in your request matches the gateway you're viewing * Check that your client received a successful response -== Step 6: Configure LLM routing (optional) +== Configure LLM routing (optional) Configure rate limits, spend limits, and provider pools with failover. @@ -247,7 +247,7 @@ On the Gateways page, select the *LLM* tab to configure routing policies. The LL For high availability, configure a fallback provider that activates when the primary fails: -. Add a second provider (for example, Anthropic) following Step 1. +. Add a second provider (for example, Anthropic). . In your gateway's *LLM* routing configuration: + * *Primary pool*: OpenAI (preferred for quality) @@ -263,7 +263,7 @@ The gateway automatically routes to the fallback when it detects: Monitor the fallback rate in observability to detect primary provider issues early. -== Step 7: Configure MCP tools (optional) +== Configure MCP tools (optional) If you're using AI agents, configure MCP (Model Context Protocol) tool aggregation. @@ -301,7 +301,7 @@ Agents then search for specific tools they need, retrieving only that subset. Th // REVIEWERS: How do users connect to the ADP catalog + MCP servers exposed through RPCN? -== Step 8: Create CEL routing rule (optional) +== Configure CEL routing rule (optional) Use CEL (Common Expression Language) expressions to route requests dynamically based on headers, content, or other request properties. @@ -555,18 +555,6 @@ const openai = new OpenAI({ }); ---- -== What you've accomplished - -After completing this quickstart, you: - -* ✓ Configured an LLM provider and enabled models -* ✓ Created your first AI Gateway -* ✓ Sent requests through the gateway -* ✓ Verified requests in the observability dashboard -* ✓ (Optional) Configured failover with provider pools -* ✓ (Optional) Created CEL routing rules -* ✓ (Optional) Set up MCP tool aggregation - == Next steps Explore advanced AI Gateway features: diff --git a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc index 5ed21f841..35fbc5054 100644 --- a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc @@ -96,7 +96,7 @@ MCP aggregation benefits: == MCP request lifecycle -=== 1. Tool discovery (initial connection) +=== Tool discovery (initial connection) Agent request: @@ -151,7 +151,7 @@ Token savings: * With deferred loading: ~500-1,000 tokens (2 tool definitions) * 80-90% reduction -=== 2. Tool query (when agent needs specific tool) +=== Tool query (when agent needs specific tool) Agent request: @@ -205,7 +205,7 @@ Gateway response: Agent receives only relevant tools based on query. -=== 3. Tool execution +=== Tool execution Agent request: diff --git a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc index bb58ba25c..ce26f9bd9 100644 --- a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc @@ -72,7 +72,7 @@ Benefits: == Step-by-step migration -=== Step 1: Add environment variables +=== Add environment variables Add gateway configuration to your environment without removing existing provider keys (yet). @@ -93,7 +93,7 @@ USE_AI_GATEWAY=false ---- -=== Step 2: Update your code +=== Update your code ==== Option A: OpenAI SDK (recommended for most use cases) @@ -303,7 +303,7 @@ else: ---- -=== Step 3: Test gateway connection +=== Test gateway connection Before changing the feature flag, verify gateway connectivity: @@ -355,7 +355,7 @@ Common issues: * `Model not found` → Ensure model is enabled in gateway configuration * No `rp-aigw-id` header → Verify header is set in `default_headers` -=== Step 4: Verify in observability dashboard +=== Verify in observability dashboard After successful test: @@ -371,7 +371,7 @@ After successful test: *If request doesn't appear*: Verify gateway ID and authentication token are correct. -=== Step 5: Enable gateway for subset of traffic +=== Enable gateway for subset of traffic Gradually roll out gateway usage: @@ -422,7 +422,7 @@ use_gateway = feature_flags.is_enabled("ai-gateway", user_context) ---- -=== Step 6: Monitor and compare +=== Monitor and compare During parallel operation, compare metrics: @@ -496,7 +496,7 @@ def call_llm_with_metrics(use_gateway: bool, model: str, messages: list): ---- -=== Step 7: Full cutover +=== Full cutover Once metrics confirm gateway reliability: @@ -724,7 +724,7 @@ Use this checklist to track your migration: == Common migration issues -=== Issue: "Model not found" error +=== "Model not found" error Symptom: [source,text] @@ -745,7 +745,7 @@ Solution: 2. Confirm format: `vendor/model_id` (for example, `openai/gpt-4o`, not `gpt-4o`) 3. Check supported models: // PLACEHOLDER: link to model catalog -=== Issue: Missing `rp-aigw-id` header +=== Missing `rp-aigw-id` header Symptom: @@ -768,7 +768,7 @@ client = OpenAI( ---- -=== Issue: Higher latency than expected +=== Higher latency than expected Expected gateway overhead: // PLACEHOLDER: Xms p50, Yms p99 @@ -781,7 +781,7 @@ If latency is significantly higher: Solution: Review geographic routing and provider pool configuration. -=== Issue: Requests not appearing in dashboard +=== Requests not appearing in dashboard Causes: @@ -791,7 +791,7 @@ Causes: Solution: Verify gateway ID and check for UI delay (logs may take a few seconds to appear). -=== Issue: Different response format +=== Different response format Symptom: Response structure differs between direct and gateway @@ -804,7 +804,7 @@ Solution: == Advanced migration scenarios -=== Scenario: Custom request timeouts +=== Custom request timeouts Before @@ -827,7 +827,7 @@ client = OpenAI( ---- -=== Scenario: Streaming responses +=== Streaming responses // PLACEHOLDER: Verify streaming support @@ -861,7 +861,7 @@ for chunk in stream: ---- -=== Scenario: Custom headers (for example, user tracking) +=== Custom headers (for example, user tracking) Before @@ -896,7 +896,7 @@ NOTE: Gateway may use custom headers for routing (for example, CEL expressions c After successful migration, you gain: -=== 1. Simplified provider management +Simplified provider management [source,python] ---- @@ -904,25 +904,24 @@ After successful migration, you gain: model = "anthropic/claude-sonnet-3.5" # Was openai/gpt-4o ---- - -=== 2. Unified observability +Unified observability * All requests in one dashboard * Cross-provider cost comparison * Session reconstruction across models -=== 3. Automatic failover +Automatic failover * Configure once, benefit everywhere * No application-level retry logic needed -=== 4. Cost controls +Cost controls * Enforce budgets centrally * Rate limit per team/customer * No surprises in cloud bills -=== 5. A/B testing +A/B testing * Test new models without code changes * Compare quality/cost/latency diff --git a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc b/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc index 31dea7fe8..b9e7d0eb6 100644 --- a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc +++ b/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc @@ -558,7 +558,7 @@ Supported integrations (if any): == Common analysis tasks -=== Task 1: "Are we staying within budget?" +=== "Are we staying within budget?" 1. View cost breakdown dashboard 2. Check budget utilization widget: @@ -575,7 +575,7 @@ Action: * If approaching limit: Adjust rate limits, optimize models, pause non-prod usage * If well under budget: Opportunity to test more expensive models -=== Task 2: "Which team is using the most resources?" +=== "Which team is using the most resources?" 1. Filter by gateway (assuming one gateway per team) 2. *Sort by Spend* (descending) @@ -606,7 +606,7 @@ Action: Action: Chargeback costs to teams, or investigate high-usage teams -=== Task 3: "Is this model worth the extra cost?" +=== "Is this model worth the extra cost?" 1. *Open Model Comparison Dashboard* 2. Select models to compare: @@ -636,7 +636,7 @@ Action: Chargeback costs to teams, or investigate high-usage teams Decision: If mini's error rate is acceptable, save 10x on costs -=== Task 4: "Why did costs spike yesterday?" +=== "Why did costs spike yesterday?" 1. View cost trend graph 2. Identify spike (e.g., Jan 10th: $500 vs usual $100) @@ -656,7 +656,7 @@ Common causes: * User error (wrong model in config) * Runaway loop in application code -=== Task 5: "Is provider X more reliable than provider Y?" +=== "Is provider X more reliable than provider Y?" 1. Open provider comparison dashboard 2. Compare error rates: From 21601b152746ce764fdb3a463dcba5fb288e3552 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 16:39:15 -0700 Subject: [PATCH 29/97] Convert learning objectives to standard format across all AI Gateway docs Updated 13 files to comply with documentation standards for learning objectives: - Added learning objective attributes to metadata (3 files) - Converted all learning objective bullets to checklist format (* [ ]) Files updated: - admin/setup-guide.adoc - builders/connect-your-agent.adoc - builders/discover-gateways.adoc - All 10 integration guide files (admin and user variants for Claude Code, Cline, Continue, Cursor, GitHub Copilot) All 23 AI Gateway files now have proper learning objective format per team standards. Co-Authored-By: Claude Sonnet 4.5 --- .../ai-agents/pages/ai-gateway/admin/setup-guide.adoc | 9 ++++++--- .../pages/ai-gateway/builders/connect-your-agent.adoc | 9 ++++++--- .../pages/ai-gateway/builders/discover-gateways.adoc | 9 ++++++--- .../pages/ai-gateway/integrations/claude-code-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/claude-code-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/cline-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/cline-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/continue-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/continue-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/cursor-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/cursor-user.adoc | 6 +++--- .../ai-gateway/integrations/github-copilot-admin.adoc | 6 +++--- .../ai-gateway/integrations/github-copilot-user.adoc | 6 +++--- 13 files changed, 48 insertions(+), 39 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc index 53363e4b1..0a0a559c7 100644 --- a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc @@ -2,6 +2,9 @@ :description: Complete setup guide for administrators to enable providers, configure models, create gateways, and set up routing policies. :page-topic-type: how-to :personas: platform_admin +:learning-objective-1: Enable LLM providers and models in the catalog +:learning-objective-2: Create and configure gateways with routing policies, rate limits, and spend limits +:learning-objective-3: Set up MCP tool aggregation for AI agents include::ai-agents:partial$ai-gateway-byoc-note.adoc[] @@ -9,9 +12,9 @@ This guide walks administrators through the complete setup process for AI Gatewa After completing this guide, you will be able to: -* Enable LLM providers and models in the catalog -* Create and configure gateways with routing policies, rate limits, and spend limits -* Set up MCP tool aggregation for AI agents +* [ ] Enable LLM providers and models in the catalog +* [ ] Create and configure gateways with routing policies, rate limits, and spend limits +* [ ] Set up MCP tool aggregation for AI agents == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc b/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc index 7abe34f00..11c99a0e3 100644 --- a/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc +++ b/modules/ai-agents/pages/ai-gateway/builders/connect-your-agent.adoc @@ -2,6 +2,9 @@ :description: Integrate your AI agent or application with Redpanda AI Gateway for unified LLM access. :page-topic-type: how-to :personas: app_developer +:learning-objective-1: Configure your application to use AI Gateway with OpenAI-compatible SDKs +:learning-objective-2: Make LLM requests through the gateway and handle responses appropriately +:learning-objective-3: Validate your integration end-to-end include::ai-agents:partial$ai-gateway-byoc-note.adoc[] @@ -9,9 +12,9 @@ This guide shows you how to connect your AI agent or application to a Redpanda A After completing this guide, you will be able to: -* Configure your application to use AI Gateway with OpenAI-compatible SDKs -* Make LLM requests through the gateway and handle responses appropriately -* Validate your integration end-to-end +* [ ] Configure your application to use AI Gateway with OpenAI-compatible SDKs +* [ ] Make LLM requests through the gateway and handle responses appropriately +* [ ] Validate your integration end-to-end == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc b/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc index 01db50d20..7e280a95e 100644 --- a/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc +++ b/modules/ai-agents/pages/ai-gateway/builders/discover-gateways.adoc @@ -2,6 +2,9 @@ :description: Find which AI Gateways you can access and their configurations. :page-topic-type: how-to :personas: app_developer +:learning-objective-1: List all AI Gateways you have access to and retrieve their endpoints and IDs +:learning-objective-2: View which models and MCP tools are available through each gateway +:learning-objective-3: Test gateway connectivity before integration include::ai-agents:partial$ai-gateway-byoc-note.adoc[] @@ -9,9 +12,9 @@ As a builder, you need to know which gateways are available to you before integr After reading this page, you will be able to: -* List all AI Gateways you have access to and retrieve their endpoints and IDs -* View which models and MCP tools are available through each gateway -* Test gateway connectivity before integration +* [ ] List all AI Gateways you have access to and retrieve their endpoints and IDs +* [ ] View which models and MCP tools are available through each gateway +* [ ] Test gateway connectivity before integration == Before you begin diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc index 3647efca4..78fe7e29f 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Claude Code clients accessing LLM provi After reading this page, you will be able to: -* Configure AI Gateway endpoints for Claude Code connectivity. -* Set up authentication and access control for Claude Code clients. -* Deploy MCP tool aggregation for Claude Code tool discovery. +* [ ] Configure AI Gateway endpoints for Claude Code connectivity. +* [ ] Set up authentication and access control for Claude Code clients. +* [ ] Deploy MCP tool aggregation for Claude Code tool discovery. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc index 8a6dcc85a..1fe805be9 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure Claude Code to connect to AI Gateway endpoints. -* Set up MCP server integration through AI Gateway. -* Verify Claude Code is routing requests through the gateway. +* [ ] Configure Claude Code to connect to AI Gateway endpoints. +* [ ] Set up MCP server integration through AI Gateway. +* [ ] Verify Claude Code is routing requests through the gateway. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc index 376927919..d66300dfe 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Cline (formerly Claude Dev) clients acc After reading this page, you will be able to: -* Configure AI Gateway endpoints for Cline connectivity. -* Set up authentication and access control for Cline clients. -* Deploy MCP tool aggregation for Cline tool discovery. +* [ ] Configure AI Gateway endpoints for Cline connectivity. +* [ ] Set up authentication and access control for Cline clients. +* [ ] Deploy MCP tool aggregation for Cline tool discovery. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc index 5e48edb25..df3345dc9 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure Cline to connect to AI Gateway for LLM requests and MCP tools. -* Set up autonomous mode with custom instructions and browser integration. -* Verify Cline routes requests through the gateway and optimize for cost. +* [ ] Configure Cline to connect to AI Gateway for LLM requests and MCP tools. +* [ ] Set up autonomous mode with custom instructions and browser integration. +* [ ] Verify Cline routes requests through the gateway and optimize for cost. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index 471128d79..63ef446c1 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Continue.dev clients accessing multiple After reading this page, you will be able to: -* Configure AI Gateway endpoints for Continue.dev connectivity. -* Set up multi-provider backends with native format routing. -* Deploy MCP tool aggregation for Continue.dev tool discovery. +* [ ] Configure AI Gateway endpoints for Continue.dev connectivity. +* [ ] Set up multi-provider backends with native format routing. +* [ ] Deploy MCP tool aggregation for Continue.dev tool discovery. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index dac4827fa..315f344af 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure Continue.dev to connect to AI Gateway for chat and autocomplete. -* Set up MCP server integration through AI Gateway. -* Optimize Continue.dev settings for cost and performance. +* [ ] Configure Continue.dev to connect to AI Gateway for chat and autocomplete. +* [ ] Set up MCP server integration through AI Gateway. +* [ ] Optimize Continue.dev settings for cost and performance. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc index 6ad5c0f58..a3e8d2c0c 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Cursor IDE clients accessing multiple L After reading this page, you will be able to: -* Configure AI Gateway endpoints for Cursor IDE connectivity. -* Set up OpenAI-compatible transforms for multi-provider routing. -* Deploy multi-tenant authentication strategies for Cursor clients. +* [ ] Configure AI Gateway endpoints for Cursor IDE connectivity. +* [ ] Set up OpenAI-compatible transforms for multi-provider routing. +* [ ] Deploy multi-tenant authentication strategies for Cursor clients. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc index 5cd74487b..3253affc5 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure Cursor IDE to route LLM requests through AI Gateway. -* Set up MCP server integration for tool access through the gateway. -* Optimize Cursor settings for multi-tenancy and cost control. +* [ ] Configure Cursor IDE to route LLM requests through AI Gateway. +* [ ] Set up MCP server integration for tool access through the gateway. +* [ ] Optimize Cursor settings for multi-tenancy and cost control. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc index b1e75933d..9a27152b5 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support GitHub Copilot clients accessing multip After reading this page, you will be able to: -* Configure AI Gateway endpoints for GitHub Copilot connectivity. -* Deploy multi-tenant authentication strategies for Copilot clients. -* Set up model aliasing and BYOK routing for GitHub Copilot. +* [ ] Configure AI Gateway endpoints for GitHub Copilot connectivity. +* [ ] Deploy multi-tenant authentication strategies for Copilot clients. +* [ ] Set up model aliasing and BYOK routing for GitHub Copilot. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc index 1896a7544..7ba95d61f 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure GitHub Copilot in VS Code and JetBrains IDEs to route requests through AI Gateway. -* Set up multi-tenancy with gateway ID headers for cost tracking. -* Configure enterprise BYOK deployments for team-wide Copilot access. +* [ ] Configure GitHub Copilot in VS Code and JetBrains IDEs to route requests through AI Gateway. +* [ ] Set up multi-tenancy with gateway ID headers for cost tracking. +* [ ] Configure enterprise BYOK deployments for team-wide Copilot access. == Prerequisites From 84e57085a403ce2c253b35efefad95ae1c177f92 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 16:54:27 -0700 Subject: [PATCH 30/97] Fix persona metadata attribute to use correct standard Changed :page-personas: to :personas: across all 19 AI Gateway files. According to the docs-team-standards plugin (content-architecture skill), the correct attribute is :personas: without the page- prefix. Files updated: - All core AI Gateway files (index, overview, architecture, quickstart) - All guide files (CEL routing, MCP aggregation, migration, observability) - All 10 integration files (admin and user variants) Co-Authored-By: Claude Sonnet 4.5 --- modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc | 2 +- modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc | 2 +- modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc | 2 +- modules/ai-agents/pages/ai-gateway/index.adoc | 2 +- .../pages/ai-gateway/integrations/claude-code-admin.adoc | 2 +- .../pages/ai-gateway/integrations/claude-code-user.adoc | 2 +- .../ai-agents/pages/ai-gateway/integrations/cline-admin.adoc | 2 +- modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc | 2 +- .../ai-agents/pages/ai-gateway/integrations/continue-admin.adoc | 2 +- .../ai-agents/pages/ai-gateway/integrations/continue-user.adoc | 2 +- .../ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc | 2 +- .../ai-agents/pages/ai-gateway/integrations/cursor-user.adoc | 2 +- .../pages/ai-gateway/integrations/github-copilot-admin.adoc | 2 +- .../pages/ai-gateway/integrations/github-copilot-user.adoc | 2 +- modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc | 2 +- modules/ai-agents/pages/ai-gateway/migration-guide.adoc | 2 +- modules/ai-agents/pages/ai-gateway/observability-logs.adoc | 2 +- modules/ai-agents/pages/ai-gateway/observability-metrics.adoc | 2 +- modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc index 352152293..0379595ee 100644 --- a/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc +++ b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc @@ -1,7 +1,7 @@ = CEL Routing Cookbook :description: CEL routing cookbook for Redpanda AI Gateway with common patterns, examples, and best practices. :page-topic-type: cookbook -:page-personas: app_developer, platform_admin +:personas: app_developer, platform_admin :learning-objective-1: Write CEL expressions to route requests based on user tier or custom headers :learning-objective-2: Test CEL routing logic using the UI editor or test requests :learning-objective-3: Troubleshoot common CEL errors using safe patterns diff --git a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc index 916b61691..c0785fccf 100644 --- a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc +++ b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc @@ -1,7 +1,7 @@ = AI Gateway Architecture :description: Technical architecture of Redpanda AI Gateway, including request lifecycle, supported providers, deployment models, and implementation details. :page-topic-type: concept -:page-personas: app_developer, platform_admin +:personas: app_developer, platform_admin :learning-objective-1: Describe the three architectural planes of AI Gateway :learning-objective-2: Explain the request lifecycle through policy evaluation stages :learning-objective-3: Identify supported providers, features, and current limitations diff --git a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc index 7c3eadb7c..5df0b6b26 100644 --- a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc +++ b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc @@ -1,7 +1,7 @@ = AI Gateway Quickstart :description: Get started with AI Gateway by configuring providers, creating your first gateway, and routing requests through unified LLM endpoints. :page-topic-type: quickstart -:page-personas: app_developer, platform_admin +:personas: app_developer, platform_admin :learning-objective-1: Enable an LLM provider and create your first gateway :learning-objective-2: Route your first request through AI Gateway and verify it works :learning-objective-3: View request logs and token usage in the observability dashboard diff --git a/modules/ai-agents/pages/ai-gateway/index.adoc b/modules/ai-agents/pages/ai-gateway/index.adoc index 8c685847b..d8be560a2 100644 --- a/modules/ai-agents/pages/ai-gateway/index.adoc +++ b/modules/ai-agents/pages/ai-gateway/index.adoc @@ -1,7 +1,7 @@ = AI Gateway :description: Unified access layer for LLM providers and AI tools with centralized routing, policy enforcement, cost management, and observability. :page-layout: index -:page-personas: platform_admin, app_developer, evaluator +:personas: platform_admin, app_developer, evaluator include::ai-agents:partial$ai-gateway-byoc-note.adoc[] diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc index 78fe7e29f..396623f96 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc @@ -1,7 +1,7 @@ = Configure AI Gateway for Claude Code :description: Configure Redpanda AI Gateway to support Claude Code clients. :page-topic-type: how-to -:page-personas: platform_admin +:personas: platform_admin :learning-objective-1: Configure AI Gateway endpoints for Claude Code connectivity :learning-objective-2: Set up authentication and access control for Claude Code clients :learning-objective-3: Deploy MCP tool aggregation for Claude Code tool discovery diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc index 1fe805be9..30160c97f 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc @@ -1,7 +1,7 @@ = Configure Claude Code with AI Gateway :description: Configure Claude Code to use Redpanda AI Gateway for unified LLM access and MCP tool aggregation. :page-topic-type: how-to -:page-personas: ai_agent_developer, app_developer +:personas: ai_agent_developer, app_developer :learning-objective-1: Configure Claude Code to connect to AI Gateway endpoints :learning-objective-2: Set up MCP server integration through AI Gateway :learning-objective-3: Verify Claude Code is routing requests through the gateway diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc index d66300dfe..3c1186be6 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc @@ -1,7 +1,7 @@ = Configure AI Gateway for Cline :description: Configure Redpanda AI Gateway to support Cline clients. :page-topic-type: how-to -:page-personas: platform_admin +:personas: platform_admin :learning-objective-1: Configure AI Gateway endpoints for Cline connectivity :learning-objective-2: Set up authentication and access control for Cline clients :learning-objective-3: Deploy MCP tool aggregation for Cline tool discovery diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc index df3345dc9..43b7c244e 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc @@ -1,7 +1,7 @@ = Configure Cline with AI Gateway :description: Configure Cline to use Redpanda AI Gateway for unified LLM access, MCP tool integration, and autonomous coding workflows. :page-topic-type: how-to -:page-personas: ai_agent_developer, app_developer +:personas: ai_agent_developer, app_developer :learning-objective-1: Configure Cline to connect to AI Gateway for LLM requests and MCP tools :learning-objective-2: Set up autonomous mode with custom instructions and browser integration :learning-objective-3: Verify Cline routes requests through the gateway and optimize for cost diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index 63ef446c1..f4f885d6a 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -1,7 +1,7 @@ = Configure AI Gateway for Continue.dev :description: Configure Redpanda AI Gateway to support Continue.dev clients. :page-topic-type: how-to -:page-personas: platform_admin +:personas: platform_admin :learning-objective-1: Configure AI Gateway endpoints for Continue.dev connectivity :learning-objective-2: Set up multi-provider backends with native format routing :learning-objective-3: Deploy MCP tool aggregation for Continue.dev tool discovery diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index 315f344af..2b1022fd5 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -1,7 +1,7 @@ = Configure Continue.dev with AI Gateway :description: Configure Continue.dev to use Redpanda AI Gateway for unified LLM access, MCP tool integration, and AI-assisted coding. :page-topic-type: how-to -:page-personas: ai_agent_developer, app_developer +:personas: ai_agent_developer, app_developer :learning-objective-1: Configure Continue.dev to connect to AI Gateway for chat and autocomplete :learning-objective-2: Set up MCP server integration through AI Gateway :learning-objective-3: Optimize Continue.dev settings for cost and performance diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc index a3e8d2c0c..55f305500 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc @@ -1,7 +1,7 @@ = Configure AI Gateway for Cursor IDE :description: Configure Redpanda AI Gateway to support Cursor IDE clients. :page-topic-type: how-to -:page-personas: platform_admin +:personas: platform_admin :learning-objective-1: Configure AI Gateway endpoints for Cursor IDE connectivity :learning-objective-2: Set up OpenAI-compatible transforms for multi-provider routing :learning-objective-3: Deploy multi-tenant authentication strategies for Cursor clients diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc index 3253affc5..3b5182176 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc @@ -1,7 +1,7 @@ = Configure Cursor IDE with AI Gateway :description: Configure Cursor IDE to use Redpanda AI Gateway for unified LLM access, MCP tool integration, and AI-assisted coding. :page-topic-type: how-to -:page-personas: ai_agent_developer, app_developer +:personas: ai_agent_developer, app_developer :learning-objective-1: Configure Cursor IDE to route LLM requests through AI Gateway :learning-objective-2: Set up MCP server integration for tool access through the gateway :learning-objective-3: Optimize Cursor settings for multi-tenancy and cost control diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc index 9a27152b5..32e20f144 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc @@ -1,7 +1,7 @@ = Configure AI Gateway for GitHub Copilot :description: Configure Redpanda AI Gateway to support GitHub Copilot clients. :page-topic-type: how-to -:page-personas: platform_admin +:personas: platform_admin :learning-objective-1: Configure AI Gateway endpoints for GitHub Copilot connectivity :learning-objective-2: Deploy multi-tenant authentication strategies for Copilot clients :learning-objective-3: Set up model aliasing and BYOK routing for GitHub Copilot diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc index 7ba95d61f..aa3aa8de4 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc @@ -1,7 +1,7 @@ = Configure GitHub Copilot with AI Gateway :description: Configure GitHub Copilot to use Redpanda AI Gateway for unified LLM access and custom provider management. :page-topic-type: how-to -:page-personas: ai_agent_developer, app_developer +:personas: ai_agent_developer, app_developer :learning-objective-1: Configure GitHub Copilot in VS Code and JetBrains IDEs to route requests through AI Gateway :learning-objective-2: Set up multi-tenancy with gateway ID headers for cost tracking :learning-objective-3: Configure enterprise BYOK deployments for team-wide Copilot access diff --git a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc index 35fbc5054..75b2b4dcf 100644 --- a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc @@ -1,7 +1,7 @@ = MCP Aggregation and Orchestration Guide :description: Guide to MCP aggregation and orchestration in Redpanda AI Gateway, including architecture, deferred tool loading, orchestrator workflows, administration, observability, security, and integration examples. :page-topic-type: guide -:page-personas: app_developer, platform_admin +:personas: app_developer, platform_admin :learning-objective-1: Configure MCP aggregation with deferred tool loading to reduce token costs :learning-objective-2: Write orchestrator workflows to reduce multi-step interactions :learning-objective-3: Manage approved MCP servers with security controls and audit trails diff --git a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc index ce26f9bd9..cf87352d5 100644 --- a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc @@ -1,7 +1,7 @@ = Migrate to AI Gateway :description: Step-by-step migration guide to transition existing applications from direct LLM provider integrations to Redpanda AI Gateway with minimal disruption. :page-topic-type: how-to -:page-personas: app_developer, platform_admin +:personas: app_developer, platform_admin :learning-objective-1: Migrate LLM integrations to AI Gateway with zero downtime using feature flags :learning-objective-2: Verify gateway connectivity and compare performance metrics :learning-objective-3: Roll back to direct integration if issues arise during migration diff --git a/modules/ai-agents/pages/ai-gateway/observability-logs.adoc b/modules/ai-agents/pages/ai-gateway/observability-logs.adoc index 01284d90f..0d1ad5455 100644 --- a/modules/ai-agents/pages/ai-gateway/observability-logs.adoc +++ b/modules/ai-agents/pages/ai-gateway/observability-logs.adoc @@ -1,7 +1,7 @@ = Observability: Logs :description: Guide to AI Gateway request logs, including where to find logs, log fields, filtering, searching, inspecting requests, common analysis tasks, log retention, export options, privacy/security, and troubleshooting. :page-topic-type: reference -:page-personas: platform_admin, app_developer +:personas: platform_admin, app_developer :learning-objective-1: Locate and filter request logs to debug failures or reconstruct conversations :learning-objective-2: Interpret log fields to diagnose performance and cost issues :learning-objective-3: Export logs for compliance auditing or long-term analysis diff --git a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc b/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc index b9e7d0eb6..4ce3512c9 100644 --- a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc +++ b/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc @@ -1,7 +1,7 @@ = Observability: Metrics and Analytics :description: Guide to AI Gateway metrics and analytics, including where to find metrics, key metrics explained, dashboard views, filtering/grouping, alerting, exporting, common analysis tasks, retention, API access, best practices, and troubleshooting. :page-topic-type: reference -:page-personas: platform_admin, app_developer +:personas: platform_admin, app_developer :learning-objective-1: Monitor aggregate metrics to track usage patterns and budget adherence :learning-objective-2: Compare model and provider performance using latency and cost metrics :learning-objective-3: Configure alerts for budget thresholds and performance degradation diff --git a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc index da31e4716..68a78a7af 100644 --- a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc @@ -1,7 +1,7 @@ = What is an AI Gateway? :description: Understand what an AI Gateway is, the problems it solves, and how it benefits your AI infrastructure. :page-topic-type: concept -:page-personas: app_developer, platform_admin +:personas: app_developer, platform_admin :learning-objective-1: Describe how AI Gateway centralizes LLM provider management and reduces operational complexity :learning-objective-2: Identify key features that address common LLM integration challenges :learning-objective-3: Determine whether AI Gateway fits your use case based on traffic volume and provider diversity From 2ab1f1c92e2f8cda3983e7d910f7c2d0a5c3e2d8 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 16:57:13 -0700 Subject: [PATCH 31/97] Fix Anthropic model identifiers in gateway quickstart examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated short invalid model names to official versioned identifiers: - claude-sonnet-3.5 → claude-3-5-sonnet-20241022 - anthropic/claude-sonnet-3.5 → anthropic/claude-3-5-sonnet-20241022 - anthropic/claude-opus-4 → anthropic/claude-opus-4-1-20250805 These changes ensure the quickstart examples use valid model IDs that match the format already used elsewhere in the file (line 478). Co-Authored-By: Claude Sonnet 4.5 --- modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc index 5df0b6b26..59292fa1a 100644 --- a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc +++ b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc @@ -38,7 +38,7 @@ AI Gateway currently supports: After enabling a provider, enable the specific models you want to make available through your gateways. . Navigate to *Models*. -. Enable the models you want to use (for example, `gpt-4o`, `gpt-4o-mini`, `claude-sonnet-3.5`). +. Enable the models you want to use (for example, `gpt-4o`, `gpt-4o-mini`, `claude-3-5-sonnet-20241022`). . Verify the models appear as "Enabled" in the catalog. TIP: Different providers have different reliability and cost characteristics. When choosing models, consider your use case requirements for quality, speed, and cost. @@ -48,7 +48,7 @@ TIP: Different providers have different reliability and cost characteristics. Wh Requests through AI Gateway must use the `vendor/model_id` format. For example: * OpenAI models: `openai/gpt-4o`, `openai/gpt-4o-mini` -* Anthropic models: `anthropic/claude-sonnet-3.5`, `anthropic/claude-opus-4` +* Anthropic models: `anthropic/claude-3-5-sonnet-20241022`, `anthropic/claude-opus-4-1-20250805` This format allows the gateway to route requests to the correct provider. From c1d3a5e94713ed3ea60084e9d241ced87d65ce62 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 16:57:50 -0700 Subject: [PATCH 32/97] Clarify VS Code settings.json does not support native env var substitution Added IMPORTANT note in the Environment-based configuration section to warn that VS Code's .vscode/settings.json does not natively support environment variable substitution with ${VAR} syntax. The note clarifies that users must either: - Install an extension that provides variable substitution - Replace placeholders manually with actual values - Set environment variables before launching VS Code This prevents confusion about the ${GATEWAY_DEV_URL} and ${GATEWAY_DEV_ID} placeholders shown in the configuration examples. Co-Authored-By: Claude Sonnet 4.5 --- modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc index 43b7c244e..cc66ca5d7 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc @@ -411,6 +411,8 @@ Debug mode shows: Use different gateways for different environments without changing settings manually. +IMPORTANT: VS Code's `.vscode/settings.json` does not natively support environment variable substitution with the `${VAR}` syntax shown below. You must either install an extension that provides variable substitution, replace the placeholders manually with actual values, or set environment variables before launching VS Code. + Create workspace-specific configurations: .Development workspace (.vscode/settings.json) From ad4c882639e754c7b56d3e475e3a3334ec1289ce Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 16:58:43 -0700 Subject: [PATCH 33/97] Change Cursor MCP endpoint to use HTTPS instead of HTTP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated the Node.js one-liner in the MCP configuration to use require('https') instead of require('http') to ensure the bearer token is sent over TLS when connecting to the gateway's MCP endpoint. Changes: - Line 269: require('http') → require('https') - Line 277: Updated description from "HTTP requests" to "HTTPS requests" This ensures secure transmission of the bearer token and other authentication credentials when Cursor connects to the AI Gateway MCP endpoint. Co-Authored-By: Claude Sonnet 4.5 --- .../ai-agents/pages/ai-gateway/integrations/cursor-user.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc index 3b5182176..24460dc55 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc @@ -266,7 +266,7 @@ Edit `settings.json` to add the MCP configuration: "command": "node", "args": [ "-e", - "require('http').request({hostname:'gw.ai.panda.com',path:'/mcp',method:'GET',headers:{'Authorization':'Bearer YOUR_REDPANDA_API_KEY','rp-aigw-id':'GATEWAY_ID'}}).end()" + "require('https').request({hostname:'gw.ai.panda.com',path:'/mcp',method:'GET',headers:{'Authorization':'Bearer YOUR_REDPANDA_API_KEY','rp-aigw-id':'GATEWAY_ID'}}).end()" ] } } @@ -274,7 +274,7 @@ Edit `settings.json` to add the MCP configuration: } ---- -This configuration uses Node.js to make HTTP requests to the gateway's MCP endpoint. The gateway returns tool definitions that Cursor can use. +This configuration uses Node.js to make HTTPS requests to the gateway's MCP endpoint. The gateway returns tool definitions that Cursor can use. Replace placeholder values: From acc0c23a379280bfeebdcab06f295a5e0dab9739 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 16:59:30 -0700 Subject: [PATCH 34/97] Fix inconsistent list markers in GitHub Copilot security section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed mixed list markers in the "Code completion security" section to use consistent bullet markers. Lines 722-723 previously used '.' (ordered list) but were changed to '*' (unordered list) to match the rest of the list starting with "GitHub Copilot sends code context to LLM providers." Changes: - Line 722: '. Proprietary code...' → '* Proprietary code...' - Line 723: '. Configure organization...' → '* Configure organization...' This ensures consistent rendering and improves AsciiDoc compliance. Co-Authored-By: Claude Sonnet 4.5 --- .../pages/ai-gateway/integrations/github-copilot-admin.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc index 32e20f144..fb2cf320f 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc @@ -719,8 +719,8 @@ Review which models GitHub Copilot clients can access: GitHub Copilot sends code context to LLM providers. Ensure: * Users understand what code context is sent with requests -. Proprietary code may be included in prompts -. Configure organization policies to limit code sharing if needed +* Proprietary code may be included in prompts +* Configure organization policies to limit code sharing if needed * Review provider data retention policies * Monitor logs for sensitive information in prompts (if logging includes prompt content) From 8742a94a49a974e5a3c92142ba4db72d711e6c23 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:00:58 -0700 Subject: [PATCH 35/97] Fix SDK examples in migration guide and remove placeholder notes Updated framework integration examples with correct implementations: 1. Anthropic SDK section (lines 238-256): - Replaced incorrect Anthropic base_url override with OpenAI client - Anthropic's base_url only works with native /v1/messages API, not OpenAI-compatible gateways - Preserved use_gateway env var logic and REDPANDA_AI_GATEWAY_* names - Removed PLACEHOLDER note 2. LlamaIndex section (lines 629-638): - Added default_headers alongside additional_kwargs for gateway headers - Removed PLACEHOLDER note about header syntax 3. Vercel AI SDK section (lines 657-671): - Changed from openai() provider to createOpenAI() for custom endpoints - Uses createOpenAI() with baseURL, apiKey, and headers configuration - Removed PLACEHOLDER note about syntax verification Co-Authored-By: Claude Sonnet 4.5 --- .../pages/ai-gateway/migration-guide.adoc | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc index cf87352d5..d80d0c373 100644 --- a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc @@ -235,23 +235,22 @@ else: ---- -Alternative: Keep Anthropic SDK with base_url override - -// PLACEHOLDER: Verify if Anthropic SDK supports base_url override for OpenAI-compatible endpoints +Alternative: Use OpenAI client for OpenAI-compatible gateway [source,python] ---- -from anthropic import Anthropic +from openai import OpenAI use_gateway = os.getenv("USE_AI_GATEWAY", "false").lower() == "true" if use_gateway: - client = Anthropic( - base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), # If supported + client = OpenAI( + base_url=os.getenv("REDPANDA_AI_GATEWAY_URL"), api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} ) else: + from anthropic import Anthropic client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) ---- @@ -631,13 +630,12 @@ if use_gateway: model="openai/gpt-4o", api_base=os.getenv("REDPANDA_AI_GATEWAY_URL"), api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), - additional_kwargs={"headers": {"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")}} + additional_kwargs={"headers": {"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")}}, + default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} ) else: llm = OpenAI(model="gpt-4o") ---- - -// PLACEHOLDER: Verify LlamaIndex syntax for custom headers -- Vercel AI SDK:: @@ -656,22 +654,21 @@ After [source,typescript] ---- +import { createOpenAI } from '@ai-sdk/openai'; import { openai } from '@ai-sdk/openai'; const useGateway = process.env.USE_AI_GATEWAY === 'true'; const model = useGateway - ? openai('openai/gpt-4o', { + ? createOpenAI({ baseURL: process.env.REDPANDA_AI_GATEWAY_URL, apiKey: process.env.REDPANDA_AI_GATEWAY_TOKEN, headers: { 'rp-aigw-id': process.env.REDPANDA_AI_GATEWAY_ID, }, - }) + })('openai/gpt-4o') : openai('gpt-4o'); ---- - -// PLACEHOLDER: Verify Vercel AI SDK syntax -- ====== From 6457cf40a1847440f1c3057de265ecb14f8e9e23 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:02:11 -0700 Subject: [PATCH 36/97] Soften hard numeric claims with qualifiers across AI Gateway docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced absolute percentage claims with conditional language to present them as typical or potential outcomes rather than guaranteed results: 1. what-is-ai-gateway.adoc: - Line 99: "can achieve 99.9% uptime" → "can significantly improve uptime (potentially up to 99.9% in some configurations)" - Line 107: "reduces token usage by 80-90%" → "often reduces token usage by 80-90% depending on your configuration and the number of tools aggregated" 2. mcp-aggregation-guide.adoc: - Line 16: "80-90% fewer tokens" → "Often 80-90% fewer tokens (depending on configuration)" - Line 152: "80-90% reduction" → "Typically 80-90% reduction" - Line 269: "Still 80-90% lower" → "Often 80-90% lower" - Line 335: "Expected Results: 80-90% reduction" → "Typically 80-90% reduction" 3. gateway-architecture.adoc: - Line 123: "80-90% token reduction" → "often 80-90% token reduction depending on configuration" These changes ensure claims are presented as conditional outcomes that depend on configuration, rather than absolute guarantees. Co-Authored-By: Claude Sonnet 4.5 --- .../ai-agents/pages/ai-gateway/gateway-architecture.adoc | 2 +- .../ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc | 8 ++++---- .../ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc index c0785fccf..a25c0d579 100644 --- a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc +++ b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc @@ -120,7 +120,7 @@ The gateway only loads and exposes specific tools when requested, which dramatic === MCP support * MCP server aggregation -* Deferred tool loading (80-90% token reduction) +* Deferred tool loading (often 80-90% token reduction depending on configuration) * JavaScript orchestrator for multi-step workflows * // PLACEHOLDER: Tool execution sandboxing? diff --git a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc index 75b2b4dcf..a5367ad38 100644 --- a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc @@ -13,7 +13,7 @@ AI Gateway provides MCP (Model Context Protocol) aggregation, allowing AI agents MCP aggregation benefits: * Single endpoint: One MCP endpoint aggregates all approved MCP servers -* Token reduction: 80-90% fewer tokens through deferred tool loading +* Token reduction: Often 80-90% fewer tokens through deferred tool loading (depending on configuration) * Centralized governance: Admin-approved MCP servers only * Orchestration: JavaScript-based orchestrator reduces multi-step round trips * Security: Controlled tool execution environment @@ -149,7 +149,7 @@ Token savings: * Without deferred loading: ~5,000-10,000 tokens (all tool definitions) * With deferred loading: ~500-1,000 tokens (2 tool definitions) -* 80-90% reduction +* Typically 80-90% reduction === Tool query (when agent needs specific tool) @@ -266,7 +266,7 @@ Deferred loading (AI Gateway): * Gateway returns matching tools * Agent calls specific tool (e.g., `execute_sql`) 5. Total token cost: Initial 500-1,000 + per-query ~200-500 - * Still 80-90% lower than loading all tools + * Often 80-90% lower than loading all tools === When to use deferred loading @@ -332,7 +332,7 @@ Compare token usage before/after deferred loading: Savings % = ((Before - After) / Before) × 100 ---- -Expected Results: 80-90% reduction in average tokens per request +Expected Results: Typically 80-90% reduction in average tokens per request == Orchestrator: multi-step workflows diff --git a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc index 68a78a7af..62e60a4d3 100644 --- a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc @@ -96,7 +96,7 @@ request.headers["x-user-tier"] == "premium" You can also set different rate limits and spend limits per environment to prevent staging or development traffic from consuming production budgets. -For reliability, you can configure provider pools with automatic failover. If you configure OpenAI GPT-4 as your primary model and Anthropic Claude Opus as the fallback, the gateway automatically routes requests to the fallback when it detects rate limits or timeouts from the primary provider. This configuration can achieve 99.9% uptime even during provider outages. +For reliability, you can configure provider pools with automatic failover. If you configure OpenAI GPT-4 as your primary model and Anthropic Claude Opus as the fallback, the gateway automatically routes requests to the fallback when it detects rate limits or timeouts from the primary provider. This configuration can significantly improve uptime (potentially up to 99.9% in some configurations) even during provider outages. === 3. MCP aggregation and orchestration @@ -104,7 +104,7 @@ AI Gateway aggregates multiple MCP (Model Context Protocol) servers and provides Without AI Gateway, agents typically load all available tools from multiple MCP servers at startup. This approach sends 50+ tool definitions with every request, creating high token costs (thousands of tokens per request), slow agent startup times, and no centralized governance over which tools agents can access. -With AI Gateway, you configure approved MCP servers once, and the gateway loads only search and orchestrator tools initially. Agents query for specific tools only when needed, which reduces token usage by 80-90% depending on your configuration. You also gain centralized approval and governance over which MCP servers your agents can access. +With AI Gateway, you configure approved MCP servers once, and the gateway loads only search and orchestrator tools initially. Agents query for specific tools only when needed, which often reduces token usage by 80-90% depending on your configuration and the number of tools aggregated. You also gain centralized approval and governance over which MCP servers your agents can access. For complex workflows, AI Gateway provides a JavaScript-based orchestrator tool that reduces multi-step workflows from multiple round trips to a single call. For example, you can create a workflow that searches a vector database and, if the results are insufficient, falls back to web search—all in one orchestration step. From 174199bd2ac100a4f0d8af67c1e9d9679230112c Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:07:01 -0700 Subject: [PATCH 37/97] Convert persona restructuring plan from Markdown to AsciiDoc Converted AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md to proper AsciiDoc format: - Renamed to lowercase: ai-gateway-persona-restructuring-plan.adoc - Converted Markdown headers (# ## ###) to AsciiDoc (= == ===) - Converted Markdown tables to AsciiDoc table format with proper column specs - Converted Markdown code blocks to AsciiDoc [source,text] blocks - Converted Markdown bold (**text**) to AsciiDoc bold (*text*) - Added blank lines before and after all tables and code blocks - Fixed all code blocks to use proper language annotations (text for trees) - Removed old markdown file All tables now have proper blank lines and all fenced code blocks have language annotations, fixing MD058/MD040 violations. Co-Authored-By: Claude Sonnet 4.5 --- .../AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md | 573 ---------------- ...ai-gateway-persona-restructuring-plan.adoc | 634 ++++++++++++++++++ 2 files changed, 634 insertions(+), 573 deletions(-) delete mode 100644 modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md create mode 100644 modules/ai-agents/partials/ai-gateway-persona-restructuring-plan.adoc diff --git a/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md b/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md deleted file mode 100644 index bc62f7e7c..000000000 --- a/modules/ai-agents/partials/AI_GATEWAY_PERSONA_RESTRUCTURING_PLAN.md +++ /dev/null @@ -1,573 +0,0 @@ -# AI Gateway Content Restructuring Plan -## Persona-Based Reorganization - -**Date:** January 21, 2026 -**Purpose:** Restructure AI Gateway documentation to align with two primary personas (Admins and Builders) and their distinct user journeys. - ---- - -## Executive Summary - -The current AI Gateway documentation is comprehensive but doesn't clearly distinguish between Admin and Builder personas. This plan proposes: - -1. **Restructure the navigation** to create clear persona-based paths -2. **Create new landing/discovery pages** for each persona -3. **Tag existing content** with appropriate personas -4. **Add missing content** to complete user journeys -5. **Reorganize the index** to guide users based on their role - ---- - -## Personas Defined - -### Admin Persona -- **Role:** Platform administrators with broad oversight -- **Responsibilities:** - - Configure system-level parameters - - Enable/disable LLM providers and models - - Set up gateways with policies, routing, and budgets - - Monitor usage across the organization - - Manage access control and security -- **Key Questions:** - - How do I set up and configure AI Gateway for my organization? - - How do I control costs and enforce policies? - - How do I monitor usage across all teams? - -### Builder Persona -- **Role:** Developers/engineers building agents or AI applications -- **Responsibilities:** - - Build agents and AI applications - - Integrate agents with available gateways - - Use MCP tools and services - - Monitor their own usage and costs -- **Key Questions:** - - Which gateways can I use? - - How do I connect my agent to a gateway? - - What tools/models are available to me? - - How much am I spending? - ---- - -## User Journey Mapping - -### Admin User Journey -1. **Understand** → What is an AI gateway? (conceptual) -2. **Set Up** → Enable providers, enable models, create gateways -3. **Configure** → Set up networking, policies, routing, budgets -4. **Monitor** → Track usage, costs, and manage access -5. **Optimize** → Adjust policies, routing, and costs based on metrics - -### Builder User Journey -1. **Discover** → Which gateways can I access? -2. **Connect** → How do I integrate my agent with a gateway? -3. **Build** → Use available models and MCP tools -4. **Test** → Validate my agent's integration -5. **Monitor** → Track my usage and costs - ---- - -## Content Gap Analysis - -### Missing Content -| Content Needed | Persona | Priority | Current Status | -|---------------|---------|----------|----------------| -| Gateway Discovery page | Builder | HIGH | Missing - critical for Builder journey | -| "What is AI Gateway" standalone page | Both | HIGH | Content exists in overview but needs extraction | -| Admin Setup Guide | Admin | HIGH | Scattered across quickstart - needs consolidation | -| Builder Integration Guide | Builder | HIGH | Exists partially in quickstart/integrations | -| Networking Configuration page | Admin | MEDIUM | Mentioned but not detailed | -| Access Management page | Admin | MEDIUM | Missing | - -### Existing Content Gaps -1. **gateway-architecture.adoc** - Too dense, mixes Admin and Builder concerns -2. **gateway-quickstart.adoc (quickstart)** - Conflates Admin setup with Builder usage -3. **index.adoc** - Too minimal, provides no guidance -4. **No discovery mechanism** - Builders don't know which gateways they can use - ---- - -## Recommended Content Structure - -### New Navigation Structure - -``` -AI Gateway/ -├── index.adoc (New: Persona-based landing page) -├── what-is-ai-gateway.adoc (New: Extracted from overview) -│ -├── For Admins/ -│ ├── admin-overview.adoc (New: Admin-focused overview) -│ ├── setup-guide.adoc (New: Complete admin setup) -│ │ ├── enable-providers.adoc (Extracted from quickstart) -│ │ ├── enable-models.adoc (Extracted from quickstart) -│ │ ├── create-gateways.adoc (Extracted from quickstart) -│ │ ├── networking-configuration.adoc (New/Expanded) -│ ├── configure-policies.adoc (Consolidated) -│ │ ├── routing-policies.adoc (Link to CEL cookbook) -│ │ ├── access-controls.adoc (New) -│ │ ├── budgets-and-limits.adoc (Consolidated from quickstart) -│ ├── manage-gateways.adoc (New: List, edit, delete) -│ ├── observability-admin.adoc (Link to metrics dashboard) -│ └── integrations/ (Admin versions) -│ ├── index.adoc -│ ├── claude-code-admin.adoc -│ ├── cursor-admin.adoc -│ └── ... -│ -├── For Builders/ -│ ├── builder-overview.adoc (New: Builder-focused overview) -│ ├── discover-gateways.adoc (NEW - CRITICAL) -│ ├── connect-your-agent.adoc (New: Integration guide) -│ ├── available-models.adoc (New: How to see what's available) -│ ├── use-mcp-tools.adoc (Link to MCP aggregation) -│ ├── test-your-integration.adoc (New: Validation) -│ ├── monitor-your-usage.adoc (Link to observability-logs) -│ └── integrations/ (Builder versions) -│ ├── index.adoc -│ ├── claude-code-user.adoc -│ ├── cursor-user.adoc -│ └── ... -│ -├── Reference/ -│ ├── gateway-architecture.adoc (Refactored: Technical deep-dive) -│ ├── cel-routing-cookbook.adoc (Existing) -│ ├── mcp-aggregation-guide.adoc (Existing) -│ ├── observability-logs.adoc (Existing) -│ ├── observability-metrics.adoc (Existing) -│ ├── migration-guide.adoc (Existing) -│ └── gateway-quickstart.adoc (Consolidated from ai-gateway.adoc and quickstart-enhanced.adoc) -``` - ---- - -## Detailed Content Recommendations - -### 1. Create New index.adoc (HIGH PRIORITY) - -**Current State:** Minimal landing page with just a description -**Proposed Change:** Transform into a persona-based router - -**Content Structure:** -```asciidoc -= AI Gateway -:description: Unified access layer for LLM providers and AI tools -:page-layout: index - -The Redpanda AI Gateway provides centralized routing, policy enforcement, cost management, and observability for all your AI traffic. - -== Choose Your Path - -[.persona-card] -=== I'm an Administrator -You manage AI Gateway infrastructure, configure providers, set policies, and monitor organizational usage. - -* xref:ai-gateway/admin/admin-overview.adoc[Admin Overview] -* xref:ai-gateway/admin/setup-guide.adoc[Setup Guide] -* xref:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] - -[.persona-card] -=== I'm a Builder -You're building AI agents or applications and need to connect to available gateways. - -* xref:ai-gateway/builders/builder-overview.adoc[Builder Overview] -* xref:ai-gateway/builders/discover-gateways.adoc[Discover Available Gateways] -* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] - -== Learn More - -* xref:ai-gateway/what-is-ai-gateway.adoc[What is an AI Gateway?] -* xref:ai-gateway/reference/gateway-architecture.adoc[Technical Architecture] -``` - -**Persona Tagging:** Both - ---- - -### 2. Create what-is-ai-gateway.adoc (HIGH PRIORITY) - -**Purpose:** Standalone conceptual page answering "What is an AI gateway?" -**Source:** Extract from gateway-architecture.adoc (lines 15-147) - -**Content to Include:** -- The problem AI Gateway solves -- Core capabilities (unified access, routing, MCP aggregation, observability) -- Common gateway patterns -- High-level architecture diagram - -**Remove from Overview:** Keep technical details in overview, move conceptual understanding here - -**Persona Tagging:** Both (Admin and Builder) - ---- - -### 3. Create discover-gateways.adoc (HIGH PRIORITY - NEW) - -**Purpose:** Help Builders find which gateways they have access to -**This is CRITICAL and completely missing from current content** - -**Content Structure:** -```asciidoc -= Discover Available Gateways -:description: Find which AI Gateways you can access and their configurations -:page-personas: app_developer - -As a builder, you need to know which gateways are available to you before integrating your agent. - -== List your accessible gateways - -=== Using the Console - -1. Navigate to AI Gateway → My Gateways -2. View all gateways you have access to: - * Gateway Name - * Gateway ID (for `rp-aigw-id` header) - * Endpoint URL - * Available Models - * MCP Tools (if configured) - -=== Using the API - -[source,bash] ----- -curl https://{CLUSTER}.cloud.redpanda.com/api/ai-gateway/v1/gateways \ - -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" ----- - -== Understanding gateway information - -Each gateway shows: - -* **Gateway ID**: Use this in the `rp-aigw-id` header -* **Endpoint URL**: Base URL for API requests -* **Available Models**: Which models you can access (e.g., `openai/gpt-4o`, `anthropic/claude-sonnet-3.5`) -* **Rate Limits**: Your request limits -* **MCP Tools**: Available MCP servers and tools (if enabled) - -== Check gateway availability - -Before integrating, test gateway access: - -[source,bash] ----- -curl https://{GATEWAY_ENDPOINT}/v1/models \ - -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ - -H "rp-aigw-id: ${GATEWAY_ID}" ----- - -Expected response: List of available models - -== Next steps - -* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] -* xref:ai-gateway/builders/available-models.adoc[View Available Models] -``` - -**Persona Tagging:** Builder (app_developer) - ---- - -### 4. Refactor gateway-quickstart.adoc (quickstart) - -**Current Problem:** Mixes Admin setup (Steps 1-3) with Builder usage (Steps 4-5, integrations) - -**Proposed Split:** - -#### Create admin/setup-guide.adoc (Admin path) -- Step 1: Enable providers -- Step 2: Enable models -- Step 3: Create gateways -- Step 4: Configure LLM routing (policies, pools, rate limits) -- Step 5: Configure MCP tools - -#### Create builders/connect-your-agent.adoc (Builder path) -- Prerequisites: Gateway ID and endpoint (from discovery) -- Step 1: Get your gateway credentials -- Step 2: Configure your client SDK -- Step 3: Make your first request -- Step 4: Handle responses -- Step 5: Validate integration - -**Content to Move:** -- Lines 17-89 (Admin steps) → admin/setup-guide.adoc -- Lines 160-337 (Integration examples) → builders/connect-your-agent.adoc -- Lines 106-118 (Observability) → Link to observability pages - ---- - -### 5. Create admin/networking-configuration.adoc (MEDIUM PRIORITY) - -**Purpose:** Dedicated page for networking setup -**Content:** Currently mentioned but not detailed - -**Content Structure:** -```asciidoc -= Networking Configuration -:description: Configure networking for AI Gateway including endpoints, private networking, and connectivity -:page-personas: platform_admin - -Configure network access and connectivity for your AI Gateway. - -== Gateway endpoints - -When you create a gateway, you receive: - -* Public endpoint: `https://gw.ai.panda.com` -* Private endpoint (if enabled): `https://gw-internal.ai.panda.com` - -== Public vs private endpoints - -**Public endpoints:** -- Accessible from internet -- Use for external agents, testing -- Standard TLS encryption - -**Private endpoints:** -- Accessible only within your VPC/network -- Use for production workloads -- Enhanced security - -== Configure private networking - -[PLACEHOLDER: Add private networking setup steps] - -== Connectivity requirements - -Outbound connections required: -- To LLM provider APIs (OpenAI, Anthropic, etc.) -- To configured MCP servers (if using MCP aggregation) - -Inbound connections: -- From your agents/applications to gateway endpoint - -== Firewall and security groups - -[PLACEHOLDER: Add security group configuration] - -== Next steps - -* xref:ai-gateway/admin/configure-policies.adoc[Configure Access Policies] -``` - -**Persona Tagging:** Admin (platform_admin) - ---- - -### 6. Create admin/access-controls.adoc (MEDIUM PRIORITY) - -**Purpose:** How Admins control who can access which gateways - -**Content:** -- Gateway-level access control -- API key management -- RBAC configuration (if available) -- Audit logging - -**Persona Tagging:** Admin (platform_admin) - ---- - -### 7. Update Existing Files - -#### gateway-architecture.adoc -**Changes:** -- Remove conceptual "What is" content (move to what-is-ai-gateway.adoc) -- Focus on technical architecture deep-dive -- Keep: Architecture details, request lifecycle, advanced patterns -- Update persona tag to: `platform_admin, app_developer` (both, but technical) - -#### cel-routing-cookbook.adoc -**Changes:** -- Add note at top: "This is an advanced reference for Admins configuring routing policies" -- Update persona tag to: `platform_admin` (currently has both) -- No content changes needed - -#### mcp-aggregation-guide.adoc -**Changes:** -- Add section for Builders: "Using MCP tools as a Builder" -- Currently too Admin-focused -- Add discovery section: How Builders see available MCP tools -- Keep persona tag: `app_developer` but clarify sections - -#### observability-logs.adoc -**Changes:** -- Add intro section distinguishing Admin vs Builder use cases: - - Admins: Monitor all traffic, all gateways, org-wide - - Builders: Monitor their own agent's requests -- Update UI paths to reflect persona-based views -- Persona tag is currently correct: `platform_admin, app_developer` - -#### observability-metrics.adoc -**Changes:** -- Similar to logs: Distinguish Admin (org-wide) vs Builder (my usage) views -- Add section: "View your agent's usage" (Builder perspective) -- Persona tag currently: `platform_admin` - should add `app_developer` - ---- - -## Navigation (nav.adoc) Changes - -**Current Structure:** -``` -* AI Gateway -** Overview -** Quickstart -** CEL Routing -** MCP Aggregation -** Observability -** Integrations -``` - -**Proposed Structure:** -``` -* xref:ai-agents:ai-gateway/index.adoc[AI Gateway] -** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[What is AI Gateway?] -** For Admins -*** xref:ai-agents:ai-gateway/admin/admin-overview.adoc[Admin Overview] -*** xref:ai-agents:ai-gateway/admin/setup-guide.adoc[Setup Guide] -*** xref:ai-agents:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] -*** xref:ai-agents:ai-gateway/admin/networking-configuration.adoc[Networking Configuration] -*** xref:ai-agents:ai-gateway/admin/configure-policies.adoc[Configure Policies] -*** xref:ai-agents:ai-gateway/admin/access-controls.adoc[Access Controls] -*** xref:ai-agents:ai-gateway/admin/observability-admin.adoc[Monitor Usage] -*** xref:ai-agents:ai-gateway/admin/integrations/index.adoc[Integrations (Admin)] -** For Builders -*** xref:ai-agents:ai-gateway/builders/builder-overview.adoc[Builder Overview] -*** xref:ai-agents:ai-gateway/builders/discover-gateways.adoc[Discover Gateways] -*** xref:ai-agents:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] -*** xref:ai-agents:ai-gateway/builders/available-models.adoc[Available Models] -*** xref:ai-agents:ai-gateway/builders/use-mcp-tools.adoc[Use MCP Tools] -*** xref:ai-agents:ai-gateway/builders/monitor-your-usage.adoc[Monitor Your Usage] -*** xref:ai-agents:ai-gateway/builders/integrations/index.adoc[Integrations (Builder)] -** Reference -*** xref:ai-agents:ai-gateway/reference/gateway-architecture.adoc[Architecture Deep Dive] -*** xref:ai-agents:ai-gateway/reference/cel-routing-cookbook.adoc[CEL Routing Cookbook] -*** xref:ai-agents:ai-gateway/reference/mcp-aggregation-guide.adoc[MCP Aggregation Guide] -*** xref:ai-agents:ai-gateway/reference/observability-logs.adoc[Request Logs] -*** xref:ai-agents:ai-gateway/reference/observability-metrics.adoc[Metrics and Analytics] -``` - ---- - -## Implementation Priority - -### Phase 1: Critical Path (Do First) -1. **Create index.adoc** - Persona router (HIGH) -2. **Create discover-gateways.adoc** - Critical Builder need (HIGH) -3. **Create what-is-ai-gateway.adoc** - Entry point (HIGH) -4. **Split quickstart** into admin/setup-guide.adoc and builders/connect-your-agent.adoc (HIGH) - -### Phase 2: Complete User Journeys -1. Create admin/manage-gateways.adoc (MEDIUM) -2. Create builders/available-models.adoc (MEDIUM) -3. Create admin/networking-configuration.adoc (MEDIUM) -4. Create admin/access-controls.adoc (MEDIUM) -5. Update observability pages with persona distinctions (MEDIUM) - -### Phase 3: Polish and Optimize -1. Refactor gateway-architecture.adoc (MEDIUM) -2. Update mcp-aggregation-guide.adoc with Builder sections (LOW) -3. Create admin/builder overview pages (LOW) -4. Reorganize integrations folders (LOW) -5. Update all cross-references (LOW) - ---- - -## Mapping to User Journey - -### Admin Journey → Content -| Journey Step | Content | -|--------------|---------| -| What is an AI gateway? | what-is-ai-gateway.adoc | -| How do I create, list, and manage gateways? | admin/setup-guide.adoc, admin/manage-gateways.adoc | -| Networking configuration & Gateway creation | admin/networking-configuration.adoc | -| Configure which models are accessible | admin/setup-guide.adoc (enable models section) | -| Configure access and routing policies | admin/configure-policies.adoc, cel-routing-cookbook.adoc | -| Track usage and configure budgeting | admin/setup-guide.adoc (budgets), observability-metrics.adoc | - -### Builder Journey → Content -| Journey Step | Content | -|--------------|---------| -| What is an AI gateway? | what-is-ai-gateway.adoc | -| Discover which AI gateways my agents have access to | **builders/discover-gateways.adoc (NEW)** | -| How do I integrate my agent? | builders/connect-your-agent.adoc | -| What models/tools are available? | builders/available-models.adoc, builders/use-mcp-tools.adoc | -| Test my integration | builders/connect-your-agent.adoc (validation section) | -| Track my usage | builders/monitor-your-usage.adoc → observability-logs.adoc | - ---- - -## Key Principles - -1. **Persona First:** Content should clearly identify which persona it serves -2. **Progressive Disclosure:** Start simple, link to advanced topics -3. **Minimize Duplication:** Use xrefs to avoid maintaining same content twice -4. **Clear Entry Points:** Index page must route users effectively -5. **Discovery is Critical:** Builders MUST be able to find available gateways - ---- - -## Success Metrics - -After implementation, evaluate: -- Can a Builder discover available gateways in <2 minutes? -- Can an Admin complete setup in <15 minutes? -- Do users report clearer distinction between Admin vs Builder tasks? -- Reduced support tickets about "I can't find which gateway to use" - ---- - -## Open Questions - -1. **API for Gateway Discovery:** Does the API support listing accessible gateways per user? -2. **RBAC Model:** How granular is access control (workspace, gateway, model level)? -3. **Private Networking:** What's the detailed setup for private endpoints? -4. **Budgets and Limits:** Can Builders see their own usage/limits, or only Admins? -5. **Integration Folders:** Should we physically split integration files into admin/ and builders/ subdirectories? - ---- - -## Next Steps - -1. **Review this plan** with product and docs team -2. **Validate API capabilities** for gateway discovery -3. **Create Phase 1 content** (index, discover-gateways, what-is, split quickstart) -4. **Test with users** from each persona -5. **Iterate based on feedback** - ---- - -## Appendix: File Operations Summary - -### New Files to Create -- `ai-gateway/index.adoc` (replace existing minimal one) -- `ai-gateway/what-is-ai-gateway.adoc` -- `ai-gateway/admin/admin-overview.adoc` -- `ai-gateway/admin/setup-guide.adoc` -- `ai-gateway/admin/manage-gateways.adoc` -- `ai-gateway/admin/networking-configuration.adoc` -- `ai-gateway/admin/configure-policies.adoc` -- `ai-gateway/admin/access-controls.adoc` -- `ai-gateway/builders/builder-overview.adoc` -- `ai-gateway/builders/discover-gateways.adoc` ⭐ CRITICAL -- `ai-gateway/builders/connect-your-agent.adoc` -- `ai-gateway/builders/available-models.adoc` -- `ai-gateway/builders/use-mcp-tools.adoc` -- `ai-gateway/builders/monitor-your-usage.adoc` - -### Files to Move -- `ai-gateway/gateway-architecture.adoc` → `ai-gateway/reference/gateway-architecture.adoc` -- `ai-gateway/cel-routing-cookbook.adoc` → `ai-gateway/reference/cel-routing-cookbook.adoc` -- `ai-gateway/mcp-aggregation-guide.adoc` → `ai-gateway/reference/mcp-aggregation-guide.adoc` -- `ai-gateway/observability-*.adoc` → `ai-gateway/reference/observability-*.adoc` - -### Files to Refactor -- `ai-gateway/gateway-quickstart.adoc` (quickstart) - split content between admin and builder paths -- `ai-gateway/gateway-architecture.adoc` - extract conceptual content to what-is page -- `ai-gateway/observability-logs.adoc` - add persona-specific sections -- `ai-gateway/observability-metrics.adoc` - add builder usage section - -### Files to Keep As-Is (Minimal Changes) -- `ai-gateway/integrations/*-admin.adoc` -- `ai-gateway/integrations/*-user.adoc` -- `ai-gateway/migration-guide.adoc` -- `ai-gateway/gateway-quickstart.adoc` (consolidated) diff --git a/modules/ai-agents/partials/ai-gateway-persona-restructuring-plan.adoc b/modules/ai-agents/partials/ai-gateway-persona-restructuring-plan.adoc new file mode 100644 index 000000000..f1062254b --- /dev/null +++ b/modules/ai-agents/partials/ai-gateway-persona-restructuring-plan.adoc @@ -0,0 +1,634 @@ += AI Gateway Content Restructuring Plan +:description: Persona-based reorganization of AI Gateway documentation + +*Date:* January 21, 2026 + +*Purpose:* Restructure AI Gateway documentation to align with two primary personas (Admins and Builders) and their distinct user journeys. + +== Executive Summary + +The current AI Gateway documentation is comprehensive but doesn't clearly distinguish between Admin and Builder personas. This plan proposes: + +. *Restructure the navigation* to create clear persona-based paths +. *Create new landing/discovery pages* for each persona +. *Tag existing content* with appropriate personas +. *Add missing content* to complete user journeys +. *Reorganize the index* to guide users based on their role + +== Personas Defined + +=== Admin Persona + +* *Role:* Platform administrators with broad oversight +* *Responsibilities:* +** Configure system-level parameters +** Enable/disable LLM providers and models +** Set up gateways with policies, routing, and budgets +** Monitor usage across the organization +** Manage access control and security +* *Key Questions:* +** How do I set up and configure AI Gateway for my organization? +** How do I control costs and enforce policies? +** How do I monitor usage across all teams? + +=== Builder Persona + +* *Role:* Developers/engineers building agents or AI applications +* *Responsibilities:* +** Build agents and AI applications +** Integrate agents with available gateways +** Use MCP tools and services +** Monitor their own usage and costs +* *Key Questions:* +** Which gateways can I use? +** How do I connect my agent to a gateway? +** What tools/models are available to me? +** How much am I spending? + +== User Journey Mapping + +=== Admin User Journey + +. *Understand* → What is an AI gateway? (conceptual) +. *Set Up* → Enable providers, enable models, create gateways +. *Configure* → Set up networking, policies, routing, budgets +. *Monitor* → Track usage, costs, and manage access +. *Optimize* → Adjust policies, routing, and costs based on metrics + +=== Builder User Journey + +. *Discover* → Which gateways can I access? +. *Connect* → How do I integrate my agent with a gateway? +. *Build* → Use available models and MCP tools +. *Test* → Validate my agent's integration +. *Monitor* → Track my usage and costs + +== Content Gap Analysis + +=== Missing Content + +[cols="1,1,1,2"] +|=== +| Content Needed | Persona | Priority | Current Status + +| Gateway Discovery page +| Builder +| HIGH +| Missing - critical for Builder journey + +| "What is AI Gateway" standalone page +| Both +| HIGH +| Content exists in overview but needs extraction + +| Admin Setup Guide +| Admin +| HIGH +| Scattered across quickstart - needs consolidation + +| Builder Integration Guide +| Builder +| HIGH +| Exists partially in quickstart/integrations + +| Networking Configuration page +| Admin +| MEDIUM +| Mentioned but not detailed + +| Access Management page +| Admin +| MEDIUM +| Missing +|=== + +=== Existing Content Gaps + +. *gateway-architecture.adoc* - Too dense, mixes Admin and Builder concerns +. *gateway-quickstart.adoc (quickstart)* - Conflates Admin setup with Builder usage +. *index.adoc* - Too minimal, provides no guidance +. *No discovery mechanism* - Builders don't know which gateways they can use + +== Recommended Content Structure + +=== New Navigation Structure + +[source,text] +---- +AI Gateway/ +├── index.adoc (New: Persona-based landing page) +├── what-is-ai-gateway.adoc (New: Extracted from overview) +│ +├── For Admins/ +│ ├── admin-overview.adoc (New: Admin-focused overview) +│ ├── setup-guide.adoc (New: Complete admin setup) +│ │ ├── enable-providers.adoc (Extracted from quickstart) +│ │ ├── enable-models.adoc (Extracted from quickstart) +│ │ ├── create-gateways.adoc (Extracted from quickstart) +│ │ ├── networking-configuration.adoc (New/Expanded) +│ ├── configure-policies.adoc (Consolidated) +│ │ ├── routing-policies.adoc (Link to CEL cookbook) +│ │ ├── access-controls.adoc (New) +│ │ ├── budgets-and-limits.adoc (Consolidated from quickstart) +│ ├── manage-gateways.adoc (New: List, edit, delete) +│ ├── observability-admin.adoc (Link to metrics dashboard) +│ └── integrations/ (Admin versions) +│ ├── index.adoc +│ ├── claude-code-admin.adoc +│ ├── cursor-admin.adoc +│ └── ... +│ +├── For Builders/ +│ ├── builder-overview.adoc (New: Builder-focused overview) +│ ├── discover-gateways.adoc (NEW - CRITICAL) +│ ├── connect-your-agent.adoc (New: Integration guide) +│ ├── available-models.adoc (New: How to see what's available) +│ ├── use-mcp-tools.adoc (Link to MCP aggregation) +│ ├── test-your-integration.adoc (New: Validation) +│ ├── monitor-your-usage.adoc (Link to observability-logs) +│ └── integrations/ (Builder versions) +│ ├── index.adoc +│ ├── claude-code-user.adoc +│ ├── cursor-user.adoc +│ └── ... +│ +├── Reference/ +│ ├── gateway-architecture.adoc (Refactored: Technical deep-dive) +│ ├── cel-routing-cookbook.adoc (Existing) +│ ├── mcp-aggregation-guide.adoc (Existing) +│ ├── observability-logs.adoc (Existing) +│ ├── observability-metrics.adoc (Existing) +│ ├── migration-guide.adoc (Existing) +│ └── gateway-quickstart.adoc (Consolidated from ai-gateway.adoc and quickstart-enhanced.adoc) +---- + +== Detailed Content Recommendations + +=== 1. Create New index.adoc (HIGH PRIORITY) + +*Current State:* Minimal landing page with just a description + +*Proposed Change:* Transform into a persona-based router + +*Content Structure:* + +[source,text] +---- += AI Gateway +:description: Unified access layer for LLM providers and AI tools +:page-layout: index + +The Redpanda AI Gateway provides centralized routing, policy enforcement, cost management, and observability for all your AI traffic. + +== Choose Your Path + +[.persona-card] +=== I'm an Administrator +You manage AI Gateway infrastructure, configure providers, set policies, and monitor organizational usage. + +* xref:ai-gateway/admin/admin-overview.adoc[Admin Overview] +* xref:ai-gateway/admin/setup-guide.adoc[Setup Guide] +* xref:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] + +[.persona-card] +=== I'm a Builder +You're building AI agents or applications and need to connect to available gateways. + +* xref:ai-gateway/builders/builder-overview.adoc[Builder Overview] +* xref:ai-gateway/builders/discover-gateways.adoc[Discover Available Gateways] +* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] + +== Learn More + +* xref:ai-gateway/what-is-ai-gateway.adoc[What is an AI Gateway?] +* xref:ai-gateway/reference/gateway-architecture.adoc[Technical Architecture] +---- + +*Persona Tagging:* Both + +=== 2. Create what-is-ai-gateway.adoc (HIGH PRIORITY) + +*Purpose:* Standalone conceptual page answering "What is an AI gateway?" + +*Source:* Extract from gateway-architecture.adoc (lines 15-147) + +*Content to Include:* + +* The problem AI Gateway solves +* Core capabilities (unified access, routing, MCP aggregation, observability) +* Common gateway patterns +* High-level architecture diagram + +*Remove from Overview:* Keep technical details in overview, move conceptual understanding here + +*Persona Tagging:* Both (Admin and Builder) + +=== 3. Create discover-gateways.adoc (HIGH PRIORITY - NEW) + +*Purpose:* Help Builders find which gateways they have access to + +*This is CRITICAL and completely missing from current content* + +*Content Structure:* + +[source,text] +---- += Discover Available Gateways +:description: Find which AI Gateways you can access and their configurations +:page-personas: app_developer + +As a builder, you need to know which gateways are available to you before integrating your agent. + +== List your accessible gateways + +=== Using the Console + +1. Navigate to AI Gateway → My Gateways +2. View all gateways you have access to: + * Gateway Name + * Gateway ID (for `rp-aigw-id` header) + * Endpoint URL + * Available Models + * MCP Tools (if configured) + +=== Using the API + +[source,bash] +---- +curl https://{CLUSTER}.cloud.redpanda.com/api/ai-gateway/v1/gateways \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" +---- + +== Understanding gateway information + +Each gateway shows: + +* *Gateway ID*: Use this in the `rp-aigw-id` header +* *Endpoint URL*: Base URL for API requests +* *Available Models*: Which models you can access (e.g., `openai/gpt-4o`, `anthropic/claude-sonnet-3.5`) +* *Rate Limits*: Your request limits +* *MCP Tools*: Available MCP servers and tools (if enabled) + +== Check gateway availability + +Before integrating, test gateway access: + +[source,bash] +---- +curl https://{GATEWAY_ENDPOINT}/v1/models \ + -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ + -H "rp-aigw-id: ${GATEWAY_ID}" +---- + +Expected response: List of available models + +== Next steps + +* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] +* xref:ai-gateway/builders/available-models.adoc[View Available Models] +---- + +*Persona Tagging:* Builder (app_developer) + +=== 4. Refactor gateway-quickstart.adoc (quickstart) + +*Current Problem:* Mixes Admin setup (Steps 1-3) with Builder usage (Steps 4-5, integrations) + +*Proposed Split:* + +==== Create admin/setup-guide.adoc (Admin path) + +* Step 1: Enable providers +* Step 2: Enable models +* Step 3: Create gateways +* Step 4: Configure LLM routing (policies, pools, rate limits) +* Step 5: Configure MCP tools + +==== Create builders/connect-your-agent.adoc (Builder path) + +* Prerequisites: Gateway ID and endpoint (from discovery) +* Step 1: Get your gateway credentials +* Step 2: Configure your client SDK +* Step 3: Make your first request +* Step 4: Handle responses +* Step 5: Validate integration + +*Content to Move:* + +* Lines 17-89 (Admin steps) → admin/setup-guide.adoc +* Lines 160-337 (Integration examples) → builders/connect-your-agent.adoc +* Lines 106-118 (Observability) → Link to observability pages + +=== 5. Create admin/networking-configuration.adoc (MEDIUM PRIORITY) + +*Purpose:* Dedicated page for networking setup + +*Content:* Currently mentioned but not detailed + +*Content Structure:* + +[source,text] +---- += Networking Configuration +:description: Configure networking for AI Gateway including endpoints, private networking, and connectivity +:page-personas: platform_admin + +Configure network access and connectivity for your AI Gateway. + +== Gateway endpoints + +When you create a gateway, you receive: + +* Public endpoint: `https://gw.ai.panda.com` +* Private endpoint (if enabled): `https://gw-internal.ai.panda.com` + +== Public vs private endpoints + +*Public endpoints:* +- Accessible from internet +- Use for external agents, testing +- Standard TLS encryption + +*Private endpoints:* +- Accessible only within your VPC/network +- Use for production workloads +- Enhanced security + +== Configure private networking + +[PLACEHOLDER: Add private networking setup steps] + +== Connectivity requirements + +Outbound connections required: +- To LLM provider APIs (OpenAI, Anthropic, etc.) +- To configured MCP servers (if using MCP aggregation) + +Inbound connections: +- From your agents/applications to gateway endpoint + +== Firewall and security groups + +[PLACEHOLDER: Add security group configuration] + +== Next steps + +* xref:ai-gateway/admin/configure-policies.adoc[Configure Access Policies] +---- + +*Persona Tagging:* Admin (platform_admin) + +=== 6. Create admin/access-controls.adoc (MEDIUM PRIORITY) + +*Purpose:* How Admins control who can access which gateways + +*Content:* + +* Gateway-level access control +* API key management +* RBAC configuration (if available) +* Audit logging + +*Persona Tagging:* Admin (platform_admin) + +=== 7. Update Existing Files + +==== gateway-architecture.adoc + +*Changes:* + +* Remove conceptual "What is" content (move to what-is-ai-gateway.adoc) +* Focus on technical architecture deep-dive +* Keep: Architecture details, request lifecycle, advanced patterns +* Update persona tag to: `platform_admin, app_developer` (both, but technical) + +==== cel-routing-cookbook.adoc + +*Changes:* + +* Add note at top: "This is an advanced reference for Admins configuring routing policies" +* Update persona tag to: `platform_admin` (currently has both) +* No content changes needed + +==== mcp-aggregation-guide.adoc + +*Changes:* + +* Add section for Builders: "Using MCP tools as a Builder" +* Currently too Admin-focused +* Add discovery section: How Builders see available MCP tools +* Keep persona tag: `app_developer` but clarify sections + +==== observability-logs.adoc + +*Changes:* + +* Add intro section distinguishing Admin vs Builder use cases: +** Admins: Monitor all traffic, all gateways, org-wide +** Builders: Monitor their own agent's requests +* Update UI paths to reflect persona-based views +* Persona tag is currently correct: `platform_admin, app_developer` + +==== observability-metrics.adoc + +*Changes:* + +* Similar to logs: Distinguish Admin (org-wide) vs Builder (my usage) views +* Add section: "View your agent's usage" (Builder perspective) +* Persona tag currently: `platform_admin` - should add `app_developer` + +== Navigation (nav.adoc) Changes + +*Current Structure:* + +[source,text] +---- +* AI Gateway +** Overview +** Quickstart +** CEL Routing +** MCP Aggregation +** Observability +** Integrations +---- + +*Proposed Structure:* + +[source,text] +---- +* xref:ai-agents:ai-gateway/index.adoc[AI Gateway] +** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[What is AI Gateway?] +** For Admins +*** xref:ai-agents:ai-gateway/admin/admin-overview.adoc[Admin Overview] +*** xref:ai-agents:ai-gateway/admin/setup-guide.adoc[Setup Guide] +*** xref:ai-agents:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] +*** xref:ai-agents:ai-gateway/admin/networking-configuration.adoc[Networking Configuration] +*** xref:ai-agents:ai-gateway/admin/configure-policies.adoc[Configure Policies] +*** xref:ai-agents:ai-gateway/admin/access-controls.adoc[Access Controls] +*** xref:ai-agents:ai-gateway/admin/observability-admin.adoc[Monitor Usage] +*** xref:ai-agents:ai-gateway/admin/integrations/index.adoc[Integrations (Admin)] +** For Builders +*** xref:ai-agents:ai-gateway/builders/builder-overview.adoc[Builder Overview] +*** xref:ai-agents:ai-gateway/builders/discover-gateways.adoc[Discover Gateways] +*** xref:ai-agents:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] +*** xref:ai-agents:ai-gateway/builders/available-models.adoc[Available Models] +*** xref:ai-agents:ai-gateway/builders/use-mcp-tools.adoc[Use MCP Tools] +*** xref:ai-agents:ai-gateway/builders/monitor-your-usage.adoc[Monitor Your Usage] +*** xref:ai-agents:ai-gateway/builders/integrations/index.adoc[Integrations (Builder)] +** Reference +*** xref:ai-agents:ai-gateway/reference/gateway-architecture.adoc[Architecture Deep Dive] +*** xref:ai-agents:ai-gateway/reference/cel-routing-cookbook.adoc[CEL Routing Cookbook] +*** xref:ai-agents:ai-gateway/reference/mcp-aggregation-guide.adoc[MCP Aggregation Guide] +*** xref:ai-agents:ai-gateway/reference/observability-logs.adoc[Request Logs] +*** xref:ai-agents:ai-gateway/reference/observability-metrics.adoc[Metrics and Analytics] +---- + +== Implementation Priority + +=== Phase 1: Critical Path (Do First) + +. *Create index.adoc* - Persona router (HIGH) +. *Create discover-gateways.adoc* - Critical Builder need (HIGH) +. *Create what-is-ai-gateway.adoc* - Entry point (HIGH) +. *Split quickstart* into admin/setup-guide.adoc and builders/connect-your-agent.adoc (HIGH) + +=== Phase 2: Complete User Journeys + +. Create admin/manage-gateways.adoc (MEDIUM) +. Create builders/available-models.adoc (MEDIUM) +. Create admin/networking-configuration.adoc (MEDIUM) +. Create admin/access-controls.adoc (MEDIUM) +. Update observability pages with persona distinctions (MEDIUM) + +=== Phase 3: Polish and Optimize + +. Refactor gateway-architecture.adoc (MEDIUM) +. Update mcp-aggregation-guide.adoc with Builder sections (LOW) +. Create admin/builder overview pages (LOW) +. Reorganize integrations folders (LOW) +. Update all cross-references (LOW) + +== Mapping to User Journey + +=== Admin Journey → Content + +[cols="1,2"] +|=== +| Journey Step | Content + +| What is an AI gateway? +| what-is-ai-gateway.adoc + +| How do I create, list, and manage gateways? +| admin/setup-guide.adoc, admin/manage-gateways.adoc + +| Networking configuration & Gateway creation +| admin/networking-configuration.adoc + +| Configure which models are accessible +| admin/setup-guide.adoc (enable models section) + +| Configure access and routing policies +| admin/configure-policies.adoc, cel-routing-cookbook.adoc + +| Track usage and configure budgeting +| admin/setup-guide.adoc (budgets), observability-metrics.adoc +|=== + +=== Builder Journey → Content + +[cols="1,2"] +|=== +| Journey Step | Content + +| What is an AI gateway? +| what-is-ai-gateway.adoc + +| Discover which AI gateways my agents have access to +| *builders/discover-gateways.adoc (NEW)* + +| How do I integrate my agent? +| builders/connect-your-agent.adoc + +| What models/tools are available? +| builders/available-models.adoc, builders/use-mcp-tools.adoc + +| Test my integration +| builders/connect-your-agent.adoc (validation section) + +| Track my usage +| builders/monitor-your-usage.adoc → observability-logs.adoc +|=== + +== Key Principles + +. *Persona First:* Content should clearly identify which persona it serves +. *Progressive Disclosure:* Start simple, link to advanced topics +. *Minimize Duplication:* Use xrefs to avoid maintaining same content twice +. *Clear Entry Points:* Index page must route users effectively +. *Discovery is Critical:* Builders MUST be able to find available gateways + +== Success Metrics + +After implementation, evaluate: + +* Can a Builder discover available gateways in <2 minutes? +* Can an Admin complete setup in <15 minutes? +* Do users report clearer distinction between Admin vs Builder tasks? +* Reduced support tickets about "I can't find which gateway to use" + +== Open Questions + +. *API for Gateway Discovery:* Does the API support listing accessible gateways per user? +. *RBAC Model:* How granular is access control (workspace, gateway, model level)? +. *Private Networking:* What's the detailed setup for private endpoints? +. *Budgets and Limits:* Can Builders see their own usage/limits, or only Admins? +. *Integration Folders:* Should we physically split integration files into admin/ and builders/ subdirectories? + +== Next Steps + +. *Review this plan* with product and docs team +. *Validate API capabilities* for gateway discovery +. *Create Phase 1 content* (index, discover-gateways, what-is, split quickstart) +. *Test with users* from each persona +. *Iterate based on feedback* + +== Appendix: File Operations Summary + +=== New Files to Create + +* `ai-gateway/index.adoc` (replace existing minimal one) +* `ai-gateway/what-is-ai-gateway.adoc` +* `ai-gateway/admin/admin-overview.adoc` +* `ai-gateway/admin/setup-guide.adoc` +* `ai-gateway/admin/manage-gateways.adoc` +* `ai-gateway/admin/networking-configuration.adoc` +* `ai-gateway/admin/configure-policies.adoc` +* `ai-gateway/admin/access-controls.adoc` +* `ai-gateway/builders/builder-overview.adoc` +* `ai-gateway/builders/discover-gateways.adoc` ⭐ CRITICAL +* `ai-gateway/builders/connect-your-agent.adoc` +* `ai-gateway/builders/available-models.adoc` +* `ai-gateway/builders/use-mcp-tools.adoc` +* `ai-gateway/builders/monitor-your-usage.adoc` + +=== Files to Move + +* `ai-gateway/gateway-architecture.adoc` → `ai-gateway/reference/gateway-architecture.adoc` +* `ai-gateway/cel-routing-cookbook.adoc` → `ai-gateway/reference/cel-routing-cookbook.adoc` +* `ai-gateway/mcp-aggregation-guide.adoc` → `ai-gateway/reference/mcp-aggregation-guide.adoc` +* `ai-gateway/observability-*.adoc` → `ai-gateway/reference/observability-*.adoc` + +=== Files to Refactor + +* `ai-gateway/gateway-quickstart.adoc` (quickstart) - split content between admin and builder paths +* `ai-gateway/gateway-architecture.adoc` - extract conceptual content to what-is page +* `ai-gateway/observability-logs.adoc` - add persona-specific sections +* `ai-gateway/observability-metrics.adoc` - add builder usage section + +=== Files to Keep As-Is (Minimal Changes) + +* `ai-gateway/integrations/*-admin.adoc` +* `ai-gateway/integrations/*-user.adoc` +* `ai-gateway/migration-guide.adoc` +* `ai-gateway/gateway-quickstart.adoc` (consolidated) From 7621fdbbc4b9a4acd66fc8965c5e499031414af1 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:07:52 -0700 Subject: [PATCH 38/97] Remove unused ai-gateway.png image file Deleted modules/shared/partials/ai-gateway.png as it's no longer needed. Co-Authored-By: Claude Sonnet 4.5 --- modules/shared/partials/ai-gateway.png | Bin 211123 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 modules/shared/partials/ai-gateway.png diff --git a/modules/shared/partials/ai-gateway.png b/modules/shared/partials/ai-gateway.png deleted file mode 100644 index 0754146d5d0268750155ab550e490c1119e4ae35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211123 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

    P=>R*cf*+is!GCyAXov|_9VpCjQB|Cj+2 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

  • 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 From 273efa9b200886d0171eaf488d12c94d33794561 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:10:10 -0700 Subject: [PATCH 39/97] Remove persona restructuring planning document Deleted ai-gateway-persona-restructuring-plan.adoc as it was an internal planning document that isn't referenced anywhere in the published docs. Planning documents don't need to be in the documentation tree. Co-Authored-By: Claude Sonnet 4.5 --- ...ai-gateway-persona-restructuring-plan.adoc | 634 ------------------ 1 file changed, 634 deletions(-) delete mode 100644 modules/ai-agents/partials/ai-gateway-persona-restructuring-plan.adoc diff --git a/modules/ai-agents/partials/ai-gateway-persona-restructuring-plan.adoc b/modules/ai-agents/partials/ai-gateway-persona-restructuring-plan.adoc deleted file mode 100644 index f1062254b..000000000 --- a/modules/ai-agents/partials/ai-gateway-persona-restructuring-plan.adoc +++ /dev/null @@ -1,634 +0,0 @@ -= AI Gateway Content Restructuring Plan -:description: Persona-based reorganization of AI Gateway documentation - -*Date:* January 21, 2026 - -*Purpose:* Restructure AI Gateway documentation to align with two primary personas (Admins and Builders) and their distinct user journeys. - -== Executive Summary - -The current AI Gateway documentation is comprehensive but doesn't clearly distinguish between Admin and Builder personas. This plan proposes: - -. *Restructure the navigation* to create clear persona-based paths -. *Create new landing/discovery pages* for each persona -. *Tag existing content* with appropriate personas -. *Add missing content* to complete user journeys -. *Reorganize the index* to guide users based on their role - -== Personas Defined - -=== Admin Persona - -* *Role:* Platform administrators with broad oversight -* *Responsibilities:* -** Configure system-level parameters -** Enable/disable LLM providers and models -** Set up gateways with policies, routing, and budgets -** Monitor usage across the organization -** Manage access control and security -* *Key Questions:* -** How do I set up and configure AI Gateway for my organization? -** How do I control costs and enforce policies? -** How do I monitor usage across all teams? - -=== Builder Persona - -* *Role:* Developers/engineers building agents or AI applications -* *Responsibilities:* -** Build agents and AI applications -** Integrate agents with available gateways -** Use MCP tools and services -** Monitor their own usage and costs -* *Key Questions:* -** Which gateways can I use? -** How do I connect my agent to a gateway? -** What tools/models are available to me? -** How much am I spending? - -== User Journey Mapping - -=== Admin User Journey - -. *Understand* → What is an AI gateway? (conceptual) -. *Set Up* → Enable providers, enable models, create gateways -. *Configure* → Set up networking, policies, routing, budgets -. *Monitor* → Track usage, costs, and manage access -. *Optimize* → Adjust policies, routing, and costs based on metrics - -=== Builder User Journey - -. *Discover* → Which gateways can I access? -. *Connect* → How do I integrate my agent with a gateway? -. *Build* → Use available models and MCP tools -. *Test* → Validate my agent's integration -. *Monitor* → Track my usage and costs - -== Content Gap Analysis - -=== Missing Content - -[cols="1,1,1,2"] -|=== -| Content Needed | Persona | Priority | Current Status - -| Gateway Discovery page -| Builder -| HIGH -| Missing - critical for Builder journey - -| "What is AI Gateway" standalone page -| Both -| HIGH -| Content exists in overview but needs extraction - -| Admin Setup Guide -| Admin -| HIGH -| Scattered across quickstart - needs consolidation - -| Builder Integration Guide -| Builder -| HIGH -| Exists partially in quickstart/integrations - -| Networking Configuration page -| Admin -| MEDIUM -| Mentioned but not detailed - -| Access Management page -| Admin -| MEDIUM -| Missing -|=== - -=== Existing Content Gaps - -. *gateway-architecture.adoc* - Too dense, mixes Admin and Builder concerns -. *gateway-quickstart.adoc (quickstart)* - Conflates Admin setup with Builder usage -. *index.adoc* - Too minimal, provides no guidance -. *No discovery mechanism* - Builders don't know which gateways they can use - -== Recommended Content Structure - -=== New Navigation Structure - -[source,text] ----- -AI Gateway/ -├── index.adoc (New: Persona-based landing page) -├── what-is-ai-gateway.adoc (New: Extracted from overview) -│ -├── For Admins/ -│ ├── admin-overview.adoc (New: Admin-focused overview) -│ ├── setup-guide.adoc (New: Complete admin setup) -│ │ ├── enable-providers.adoc (Extracted from quickstart) -│ │ ├── enable-models.adoc (Extracted from quickstart) -│ │ ├── create-gateways.adoc (Extracted from quickstart) -│ │ ├── networking-configuration.adoc (New/Expanded) -│ ├── configure-policies.adoc (Consolidated) -│ │ ├── routing-policies.adoc (Link to CEL cookbook) -│ │ ├── access-controls.adoc (New) -│ │ ├── budgets-and-limits.adoc (Consolidated from quickstart) -│ ├── manage-gateways.adoc (New: List, edit, delete) -│ ├── observability-admin.adoc (Link to metrics dashboard) -│ └── integrations/ (Admin versions) -│ ├── index.adoc -│ ├── claude-code-admin.adoc -│ ├── cursor-admin.adoc -│ └── ... -│ -├── For Builders/ -│ ├── builder-overview.adoc (New: Builder-focused overview) -│ ├── discover-gateways.adoc (NEW - CRITICAL) -│ ├── connect-your-agent.adoc (New: Integration guide) -│ ├── available-models.adoc (New: How to see what's available) -│ ├── use-mcp-tools.adoc (Link to MCP aggregation) -│ ├── test-your-integration.adoc (New: Validation) -│ ├── monitor-your-usage.adoc (Link to observability-logs) -│ └── integrations/ (Builder versions) -│ ├── index.adoc -│ ├── claude-code-user.adoc -│ ├── cursor-user.adoc -│ └── ... -│ -├── Reference/ -│ ├── gateway-architecture.adoc (Refactored: Technical deep-dive) -│ ├── cel-routing-cookbook.adoc (Existing) -│ ├── mcp-aggregation-guide.adoc (Existing) -│ ├── observability-logs.adoc (Existing) -│ ├── observability-metrics.adoc (Existing) -│ ├── migration-guide.adoc (Existing) -│ └── gateway-quickstart.adoc (Consolidated from ai-gateway.adoc and quickstart-enhanced.adoc) ----- - -== Detailed Content Recommendations - -=== 1. Create New index.adoc (HIGH PRIORITY) - -*Current State:* Minimal landing page with just a description - -*Proposed Change:* Transform into a persona-based router - -*Content Structure:* - -[source,text] ----- -= AI Gateway -:description: Unified access layer for LLM providers and AI tools -:page-layout: index - -The Redpanda AI Gateway provides centralized routing, policy enforcement, cost management, and observability for all your AI traffic. - -== Choose Your Path - -[.persona-card] -=== I'm an Administrator -You manage AI Gateway infrastructure, configure providers, set policies, and monitor organizational usage. - -* xref:ai-gateway/admin/admin-overview.adoc[Admin Overview] -* xref:ai-gateway/admin/setup-guide.adoc[Setup Guide] -* xref:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] - -[.persona-card] -=== I'm a Builder -You're building AI agents or applications and need to connect to available gateways. - -* xref:ai-gateway/builders/builder-overview.adoc[Builder Overview] -* xref:ai-gateway/builders/discover-gateways.adoc[Discover Available Gateways] -* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] - -== Learn More - -* xref:ai-gateway/what-is-ai-gateway.adoc[What is an AI Gateway?] -* xref:ai-gateway/reference/gateway-architecture.adoc[Technical Architecture] ----- - -*Persona Tagging:* Both - -=== 2. Create what-is-ai-gateway.adoc (HIGH PRIORITY) - -*Purpose:* Standalone conceptual page answering "What is an AI gateway?" - -*Source:* Extract from gateway-architecture.adoc (lines 15-147) - -*Content to Include:* - -* The problem AI Gateway solves -* Core capabilities (unified access, routing, MCP aggregation, observability) -* Common gateway patterns -* High-level architecture diagram - -*Remove from Overview:* Keep technical details in overview, move conceptual understanding here - -*Persona Tagging:* Both (Admin and Builder) - -=== 3. Create discover-gateways.adoc (HIGH PRIORITY - NEW) - -*Purpose:* Help Builders find which gateways they have access to - -*This is CRITICAL and completely missing from current content* - -*Content Structure:* - -[source,text] ----- -= Discover Available Gateways -:description: Find which AI Gateways you can access and their configurations -:page-personas: app_developer - -As a builder, you need to know which gateways are available to you before integrating your agent. - -== List your accessible gateways - -=== Using the Console - -1. Navigate to AI Gateway → My Gateways -2. View all gateways you have access to: - * Gateway Name - * Gateway ID (for `rp-aigw-id` header) - * Endpoint URL - * Available Models - * MCP Tools (if configured) - -=== Using the API - -[source,bash] ----- -curl https://{CLUSTER}.cloud.redpanda.com/api/ai-gateway/v1/gateways \ - -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" ----- - -== Understanding gateway information - -Each gateway shows: - -* *Gateway ID*: Use this in the `rp-aigw-id` header -* *Endpoint URL*: Base URL for API requests -* *Available Models*: Which models you can access (e.g., `openai/gpt-4o`, `anthropic/claude-sonnet-3.5`) -* *Rate Limits*: Your request limits -* *MCP Tools*: Available MCP servers and tools (if enabled) - -== Check gateway availability - -Before integrating, test gateway access: - -[source,bash] ----- -curl https://{GATEWAY_ENDPOINT}/v1/models \ - -H "Authorization: Bearer ${REDPANDA_CLOUD_TOKEN}" \ - -H "rp-aigw-id: ${GATEWAY_ID}" ----- - -Expected response: List of available models - -== Next steps - -* xref:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] -* xref:ai-gateway/builders/available-models.adoc[View Available Models] ----- - -*Persona Tagging:* Builder (app_developer) - -=== 4. Refactor gateway-quickstart.adoc (quickstart) - -*Current Problem:* Mixes Admin setup (Steps 1-3) with Builder usage (Steps 4-5, integrations) - -*Proposed Split:* - -==== Create admin/setup-guide.adoc (Admin path) - -* Step 1: Enable providers -* Step 2: Enable models -* Step 3: Create gateways -* Step 4: Configure LLM routing (policies, pools, rate limits) -* Step 5: Configure MCP tools - -==== Create builders/connect-your-agent.adoc (Builder path) - -* Prerequisites: Gateway ID and endpoint (from discovery) -* Step 1: Get your gateway credentials -* Step 2: Configure your client SDK -* Step 3: Make your first request -* Step 4: Handle responses -* Step 5: Validate integration - -*Content to Move:* - -* Lines 17-89 (Admin steps) → admin/setup-guide.adoc -* Lines 160-337 (Integration examples) → builders/connect-your-agent.adoc -* Lines 106-118 (Observability) → Link to observability pages - -=== 5. Create admin/networking-configuration.adoc (MEDIUM PRIORITY) - -*Purpose:* Dedicated page for networking setup - -*Content:* Currently mentioned but not detailed - -*Content Structure:* - -[source,text] ----- -= Networking Configuration -:description: Configure networking for AI Gateway including endpoints, private networking, and connectivity -:page-personas: platform_admin - -Configure network access and connectivity for your AI Gateway. - -== Gateway endpoints - -When you create a gateway, you receive: - -* Public endpoint: `https://gw.ai.panda.com` -* Private endpoint (if enabled): `https://gw-internal.ai.panda.com` - -== Public vs private endpoints - -*Public endpoints:* -- Accessible from internet -- Use for external agents, testing -- Standard TLS encryption - -*Private endpoints:* -- Accessible only within your VPC/network -- Use for production workloads -- Enhanced security - -== Configure private networking - -[PLACEHOLDER: Add private networking setup steps] - -== Connectivity requirements - -Outbound connections required: -- To LLM provider APIs (OpenAI, Anthropic, etc.) -- To configured MCP servers (if using MCP aggregation) - -Inbound connections: -- From your agents/applications to gateway endpoint - -== Firewall and security groups - -[PLACEHOLDER: Add security group configuration] - -== Next steps - -* xref:ai-gateway/admin/configure-policies.adoc[Configure Access Policies] ----- - -*Persona Tagging:* Admin (platform_admin) - -=== 6. Create admin/access-controls.adoc (MEDIUM PRIORITY) - -*Purpose:* How Admins control who can access which gateways - -*Content:* - -* Gateway-level access control -* API key management -* RBAC configuration (if available) -* Audit logging - -*Persona Tagging:* Admin (platform_admin) - -=== 7. Update Existing Files - -==== gateway-architecture.adoc - -*Changes:* - -* Remove conceptual "What is" content (move to what-is-ai-gateway.adoc) -* Focus on technical architecture deep-dive -* Keep: Architecture details, request lifecycle, advanced patterns -* Update persona tag to: `platform_admin, app_developer` (both, but technical) - -==== cel-routing-cookbook.adoc - -*Changes:* - -* Add note at top: "This is an advanced reference for Admins configuring routing policies" -* Update persona tag to: `platform_admin` (currently has both) -* No content changes needed - -==== mcp-aggregation-guide.adoc - -*Changes:* - -* Add section for Builders: "Using MCP tools as a Builder" -* Currently too Admin-focused -* Add discovery section: How Builders see available MCP tools -* Keep persona tag: `app_developer` but clarify sections - -==== observability-logs.adoc - -*Changes:* - -* Add intro section distinguishing Admin vs Builder use cases: -** Admins: Monitor all traffic, all gateways, org-wide -** Builders: Monitor their own agent's requests -* Update UI paths to reflect persona-based views -* Persona tag is currently correct: `platform_admin, app_developer` - -==== observability-metrics.adoc - -*Changes:* - -* Similar to logs: Distinguish Admin (org-wide) vs Builder (my usage) views -* Add section: "View your agent's usage" (Builder perspective) -* Persona tag currently: `platform_admin` - should add `app_developer` - -== Navigation (nav.adoc) Changes - -*Current Structure:* - -[source,text] ----- -* AI Gateway -** Overview -** Quickstart -** CEL Routing -** MCP Aggregation -** Observability -** Integrations ----- - -*Proposed Structure:* - -[source,text] ----- -* xref:ai-agents:ai-gateway/index.adoc[AI Gateway] -** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[What is AI Gateway?] -** For Admins -*** xref:ai-agents:ai-gateway/admin/admin-overview.adoc[Admin Overview] -*** xref:ai-agents:ai-gateway/admin/setup-guide.adoc[Setup Guide] -*** xref:ai-agents:ai-gateway/admin/manage-gateways.adoc[Manage Gateways] -*** xref:ai-agents:ai-gateway/admin/networking-configuration.adoc[Networking Configuration] -*** xref:ai-agents:ai-gateway/admin/configure-policies.adoc[Configure Policies] -*** xref:ai-agents:ai-gateway/admin/access-controls.adoc[Access Controls] -*** xref:ai-agents:ai-gateway/admin/observability-admin.adoc[Monitor Usage] -*** xref:ai-agents:ai-gateway/admin/integrations/index.adoc[Integrations (Admin)] -** For Builders -*** xref:ai-agents:ai-gateway/builders/builder-overview.adoc[Builder Overview] -*** xref:ai-agents:ai-gateway/builders/discover-gateways.adoc[Discover Gateways] -*** xref:ai-agents:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] -*** xref:ai-agents:ai-gateway/builders/available-models.adoc[Available Models] -*** xref:ai-agents:ai-gateway/builders/use-mcp-tools.adoc[Use MCP Tools] -*** xref:ai-agents:ai-gateway/builders/monitor-your-usage.adoc[Monitor Your Usage] -*** xref:ai-agents:ai-gateway/builders/integrations/index.adoc[Integrations (Builder)] -** Reference -*** xref:ai-agents:ai-gateway/reference/gateway-architecture.adoc[Architecture Deep Dive] -*** xref:ai-agents:ai-gateway/reference/cel-routing-cookbook.adoc[CEL Routing Cookbook] -*** xref:ai-agents:ai-gateway/reference/mcp-aggregation-guide.adoc[MCP Aggregation Guide] -*** xref:ai-agents:ai-gateway/reference/observability-logs.adoc[Request Logs] -*** xref:ai-agents:ai-gateway/reference/observability-metrics.adoc[Metrics and Analytics] ----- - -== Implementation Priority - -=== Phase 1: Critical Path (Do First) - -. *Create index.adoc* - Persona router (HIGH) -. *Create discover-gateways.adoc* - Critical Builder need (HIGH) -. *Create what-is-ai-gateway.adoc* - Entry point (HIGH) -. *Split quickstart* into admin/setup-guide.adoc and builders/connect-your-agent.adoc (HIGH) - -=== Phase 2: Complete User Journeys - -. Create admin/manage-gateways.adoc (MEDIUM) -. Create builders/available-models.adoc (MEDIUM) -. Create admin/networking-configuration.adoc (MEDIUM) -. Create admin/access-controls.adoc (MEDIUM) -. Update observability pages with persona distinctions (MEDIUM) - -=== Phase 3: Polish and Optimize - -. Refactor gateway-architecture.adoc (MEDIUM) -. Update mcp-aggregation-guide.adoc with Builder sections (LOW) -. Create admin/builder overview pages (LOW) -. Reorganize integrations folders (LOW) -. Update all cross-references (LOW) - -== Mapping to User Journey - -=== Admin Journey → Content - -[cols="1,2"] -|=== -| Journey Step | Content - -| What is an AI gateway? -| what-is-ai-gateway.adoc - -| How do I create, list, and manage gateways? -| admin/setup-guide.adoc, admin/manage-gateways.adoc - -| Networking configuration & Gateway creation -| admin/networking-configuration.adoc - -| Configure which models are accessible -| admin/setup-guide.adoc (enable models section) - -| Configure access and routing policies -| admin/configure-policies.adoc, cel-routing-cookbook.adoc - -| Track usage and configure budgeting -| admin/setup-guide.adoc (budgets), observability-metrics.adoc -|=== - -=== Builder Journey → Content - -[cols="1,2"] -|=== -| Journey Step | Content - -| What is an AI gateway? -| what-is-ai-gateway.adoc - -| Discover which AI gateways my agents have access to -| *builders/discover-gateways.adoc (NEW)* - -| How do I integrate my agent? -| builders/connect-your-agent.adoc - -| What models/tools are available? -| builders/available-models.adoc, builders/use-mcp-tools.adoc - -| Test my integration -| builders/connect-your-agent.adoc (validation section) - -| Track my usage -| builders/monitor-your-usage.adoc → observability-logs.adoc -|=== - -== Key Principles - -. *Persona First:* Content should clearly identify which persona it serves -. *Progressive Disclosure:* Start simple, link to advanced topics -. *Minimize Duplication:* Use xrefs to avoid maintaining same content twice -. *Clear Entry Points:* Index page must route users effectively -. *Discovery is Critical:* Builders MUST be able to find available gateways - -== Success Metrics - -After implementation, evaluate: - -* Can a Builder discover available gateways in <2 minutes? -* Can an Admin complete setup in <15 minutes? -* Do users report clearer distinction between Admin vs Builder tasks? -* Reduced support tickets about "I can't find which gateway to use" - -== Open Questions - -. *API for Gateway Discovery:* Does the API support listing accessible gateways per user? -. *RBAC Model:* How granular is access control (workspace, gateway, model level)? -. *Private Networking:* What's the detailed setup for private endpoints? -. *Budgets and Limits:* Can Builders see their own usage/limits, or only Admins? -. *Integration Folders:* Should we physically split integration files into admin/ and builders/ subdirectories? - -== Next Steps - -. *Review this plan* with product and docs team -. *Validate API capabilities* for gateway discovery -. *Create Phase 1 content* (index, discover-gateways, what-is, split quickstart) -. *Test with users* from each persona -. *Iterate based on feedback* - -== Appendix: File Operations Summary - -=== New Files to Create - -* `ai-gateway/index.adoc` (replace existing minimal one) -* `ai-gateway/what-is-ai-gateway.adoc` -* `ai-gateway/admin/admin-overview.adoc` -* `ai-gateway/admin/setup-guide.adoc` -* `ai-gateway/admin/manage-gateways.adoc` -* `ai-gateway/admin/networking-configuration.adoc` -* `ai-gateway/admin/configure-policies.adoc` -* `ai-gateway/admin/access-controls.adoc` -* `ai-gateway/builders/builder-overview.adoc` -* `ai-gateway/builders/discover-gateways.adoc` ⭐ CRITICAL -* `ai-gateway/builders/connect-your-agent.adoc` -* `ai-gateway/builders/available-models.adoc` -* `ai-gateway/builders/use-mcp-tools.adoc` -* `ai-gateway/builders/monitor-your-usage.adoc` - -=== Files to Move - -* `ai-gateway/gateway-architecture.adoc` → `ai-gateway/reference/gateway-architecture.adoc` -* `ai-gateway/cel-routing-cookbook.adoc` → `ai-gateway/reference/cel-routing-cookbook.adoc` -* `ai-gateway/mcp-aggregation-guide.adoc` → `ai-gateway/reference/mcp-aggregation-guide.adoc` -* `ai-gateway/observability-*.adoc` → `ai-gateway/reference/observability-*.adoc` - -=== Files to Refactor - -* `ai-gateway/gateway-quickstart.adoc` (quickstart) - split content between admin and builder paths -* `ai-gateway/gateway-architecture.adoc` - extract conceptual content to what-is page -* `ai-gateway/observability-logs.adoc` - add persona-specific sections -* `ai-gateway/observability-metrics.adoc` - add builder usage section - -=== Files to Keep As-Is (Minimal Changes) - -* `ai-gateway/integrations/*-admin.adoc` -* `ai-gateway/integrations/*-user.adoc` -* `ai-gateway/migration-guide.adoc` -* `ai-gateway/gateway-quickstart.adoc` (consolidated) From 3de2abf1f401f046f05e8058c8f9987ea473a310 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:30:15 -0700 Subject: [PATCH 40/97] Update Claude Code config to match Anthropic MCP schema - Change config file path from ~/.claude/config.json to ~/.claude.json - Add mention of project-level .mcp.json alternative - Replace "transport": "http" with "type": "http" per MCP spec - Remove undocumented apiProviders sections from all examples - Remove request retry configuration section (used apiProviders) - Remove troubleshooting item about missing apiProviders - Update all troubleshooting commands to use correct file path Files updated: - claude-code-admin.adoc - claude-code-user.adoc Co-Authored-By: Claude Sonnet 4.5 --- .../integrations/claude-code-admin.adoc | 13 +-- .../integrations/claude-code-user.adoc | 94 +++---------------- 2 files changed, 16 insertions(+), 91 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc index 396623f96..5bbb2e844 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc @@ -328,28 +328,20 @@ Replace: === Configuration file -Alternatively, users can edit `~/.claude/config.json`: +Alternatively, users can edit `~/.claude.json` (user-level) or `.mcp.json` (project-level): [source,json] ---- { "mcpServers": { "redpanda-ai-gateway": { - "transport": "http", + "type": "http", "url": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/mcp", "headers": { "Authorization": "Bearer YOUR_API_TOKEN", "rp-aigw-id": "GATEWAY_ID" } } - }, - "apiProviders": { - "redpanda": { - "baseURL": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1", - "headers": { - "rp-aigw-id": "GATEWAY_ID" - } - } } } ---- @@ -357,7 +349,6 @@ Alternatively, users can edit `~/.claude/config.json`: This configuration: * Connects Claude Code to the aggregated MCP endpoint -* Routes LLM requests through the AI Gateway * Includes authentication and gateway identification headers == Monitor Claude Code usage diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc index 30160c97f..41132c491 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc @@ -94,40 +94,25 @@ For more complex configurations or when managing multiple gateways, edit the Cla Claude Code stores configuration in: -* macOS/Linux: `~/.claude/config.json` -* Windows: `%USERPROFILE%\.claude\config.json` - -Create the directory if it doesn't exist: - -[,bash] ----- -mkdir -p ~/.claude ----- +* macOS/Linux: `~/.claude.json` (user-level) or `.mcp.json` (project-level) +* Windows: `%USERPROFILE%\.claude.json` === Basic configuration -Create or edit `~/.claude/config.json` with the following structure: +Create or edit `~/.claude.json` with the following structure: [,json] ---- { "mcpServers": { "redpanda-ai-gateway": { - "transport": "http", + "type": "http", "url": "https://gw.ai.panda.com/mcp", "headers": { "Authorization": "Bearer YOUR_API_KEY", "rp-aigw-id": "GATEWAY_ID" } } - }, - "apiProviders": { - "redpanda": { - "baseURL": "https://gw.ai.panda.com", - "headers": { - "rp-aigw-id": "GATEWAY_ID" - } - } } } ---- @@ -146,7 +131,7 @@ To configure different gateways for development and production: { "mcpServers": { "redpanda-staging": { - "transport": "http", + "type": "http", "url": "https://gw.staging.ai.panda.com/mcp", "headers": { "Authorization": "Bearer STAGING_API_KEY", @@ -154,32 +139,18 @@ To configure different gateways for development and production: } }, "redpanda-production": { - "transport": "http", + "type": "http", "url": "https://gw.ai.panda.com/mcp", "headers": { "Authorization": "Bearer PROD_API_KEY", "rp-aigw-id": "prod-gateway-456" } } - }, - "apiProviders": { - "redpanda-staging": { - "baseURL": "https://gw.staging.ai.panda.com", - "headers": { - "rp-aigw-id": "staging-gateway-123" - } - }, - "redpanda-production": { - "baseURL": "https://gw.ai.panda.com", - "headers": { - "rp-aigw-id": "prod-gateway-456" - } - } } } ---- -Switch between gateways by selecting the appropriate MCP server or API provider when using Claude Code. +Switch between gateways by selecting the appropriate MCP server when using Claude Code. === Configuration with environment variables @@ -190,21 +161,13 @@ For sensitive credentials, use environment variables instead of hardcoding value { "mcpServers": { "redpanda-ai-gateway": { - "transport": "http", + "type": "http", "url": "${REDPANDA_GATEWAY_URL}/mcp", "headers": { "Authorization": "Bearer ${REDPANDA_API_KEY}", "rp-aigw-id": "${REDPANDA_GATEWAY_ID}" } } - }, - "apiProviders": { - "redpanda": { - "baseURL": "${REDPANDA_GATEWAY_URL}", - "headers": { - "rp-aigw-id": "${REDPANDA_GATEWAY_ID}" - } - } } } ---- @@ -280,7 +243,7 @@ Configure timeout for MCP requests in the configuration file: { "mcpServers": { "redpanda-ai-gateway": { - "transport": "http", + "type": "http", "url": "https://gw.ai.panda.com/mcp", "headers": { "Authorization": "Bearer YOUR_API_KEY", @@ -294,31 +257,6 @@ Configure timeout for MCP requests in the configuration file: The `timeout` value is in milliseconds. Default is 10000 (10 seconds). Increase this for MCP tools that perform long-running operations. -=== Request retry configuration - -Configure retry behavior for transient failures: - -[,json] ----- -{ - "apiProviders": { - "redpanda": { - "baseURL": "https://gw.ai.panda.com", - "headers": { - "rp-aigw-id": "GATEWAY_ID" - }, - "retry": { - "maxRetries": 3, - "retryDelay": 1000, - "retryCondition": ["5xx", "timeout"] - } - } - } -} ----- - -This configuration retries requests up to 3 times on server errors (5xx status codes) or timeouts, with a 1-second delay between retries. - === Debug mode Enable debug logging to troubleshoot connection issues: @@ -396,10 +334,6 @@ If this times out, check your network configuration, firewall rules, or VPN conn + Verify that the `rp-aigw-id` header in your configuration matches the gateway you're viewing in the dashboard. -. **Using direct Anthropic API** -+ -If you didn't configure the `apiProviders` section, Claude Code may be routing directly to Anthropic instead of through your gateway. Verify the `apiProviders` section exists in your config file. - . **Log ingestion delay** + Gateway logs can take 5-10 seconds to appear in the dashboard. Wait briefly and refresh. @@ -432,7 +366,7 @@ If you're hitting rate limits, the gateway may be queuing requests. Check the ob === Configuration file not loading -**Symptom**: Changes to `config.json` don't take effect. +**Symptom**: Changes to `.claude.json` don't take effect. **Solutions**: @@ -451,11 +385,11 @@ claude . **Validate JSON syntax** + -Ensure your `config.json` is valid JSON. Use a JSON validator: +Ensure your `.claude.json` is valid JSON. Use a JSON validator: + [,bash] ---- -python3 -m json.tool ~/.claude/config.json +python3 -m json.tool ~/.claude.json ---- . **Check file permissions** @@ -464,14 +398,14 @@ Verify Claude Code can read the configuration file: + [,bash] ---- -ls -la ~/.claude/config.json +ls -la ~/.claude.json ---- + The file should be readable by your user. If not, fix permissions: + [,bash] ---- -chmod 600 ~/.claude/config.json +chmod 600 ~/.claude.json ---- == Next steps From 2d3c01878c7d7e7a90ffd367be06dbcae6efea46 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:46:11 -0700 Subject: [PATCH 41/97] Add note about environment variable interpolation in mcpServers Clarify that Claude Code supports ${VAR} syntax for environment variable interpolation in the mcpServers section, specifying which variables will be resolved at runtime (REDPANDA_GATEWAY_URL, REDPANDA_GATEWAY_ID, REDPANDA_API_KEY). Co-Authored-By: Claude Sonnet 4.5 --- .../pages/ai-gateway/integrations/claude-code-user.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc index 41132c491..7fb085124 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc @@ -172,6 +172,8 @@ For sensitive credentials, use environment variables instead of hardcoding value } ---- +NOTE: Claude Code supports `${VAR}` interpolation syntax in the `mcpServers` section. The variables `REDPANDA_GATEWAY_URL`, `REDPANDA_GATEWAY_ID`, and `REDPANDA_API_KEY` will be resolved from environment variables at runtime. + Set environment variables before launching Claude Code: [,bash] From 6c2b01c289146cb5df904c4b8a295bc82166df9f Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:47:55 -0700 Subject: [PATCH 42/97] Update Cline config to use official extension settings Remove unofficial VS Code settings keys (cline.apiProvider, cline.apiBaseUrl, cline.customHeaders) and document that API/provider configuration is managed via Cline extension global state, not settings.json. Replace MCP configuration to use cline_mcp_settings.json with official schema: - Change "transport": "http" to "type": "streamableHttp" - Remove "cline.mcpServers" wrapper (use "mcpServers" directly) - Add "Cline > Mcp: Mode" toggle for MCP enablement - Document Cline UI and cline_mcp_settings.json as config methods Update configuration scope section to reflect extension global state storage model instead of VS Code settings.json levels. Co-Authored-By: Claude Sonnet 4.5 --- .../ai-gateway/integrations/cline-admin.adoc | 87 +++++++++---------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc index 3c1186be6..64fb52b7c 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc @@ -327,35 +327,22 @@ Implement token rotation for security: Provide these instructions to users configuring Cline in VS Code. -=== VS Code settings configuration +=== API provider configuration -Users configure Cline through VS Code settings (either UI or `settings.json`). +Users configure Cline's API provider and credentials through the Cline extension interface. -==== Using VS Code Settings UI +IMPORTANT: API provider configuration (API keys, base URLs, custom headers) is managed via Cline's extension global state, not VS Code `settings.json`. These settings are stored in the extension's internal state and must be configured through the Cline UI. -. Open VS Code Settings (Cmd/Ctrl + ,) -. Search for "Cline" -. Configure the following settings: -+ -* *Cline: API Provider*: Select "Custom" or "Anthropic" -* *Cline: API Base URL*: `https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1` -* *Cline: API Key*: The API token generated earlier - -==== Using settings.json - -Alternatively, users can edit `.vscode/settings.json` in their workspace: +==== Configure via Cline UI -[source,json] ----- -{ - "cline.apiProvider": "custom", - "cline.apiBaseUrl": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1", - "cline.apiKey": "YOUR_API_TOKEN", - "cline.customHeaders": { - "rp-aigw-id": "GATEWAY_ID" - } -} ----- +. Open the Cline extension panel in VS Code +. Click the settings icon or gear menu +. Configure the API connection: ++ +* *API Provider*: Select "Custom" or "Anthropic" +* *API Base URL*: `https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1` +* *API Key*: The API token generated earlier +* *Custom Headers*: Add `rp-aigw-id` with value `GATEWAY_ID` Replace: @@ -365,14 +352,30 @@ Replace: === MCP server configuration -Configure Cline to connect to the aggregated MCP endpoint: +Configure Cline to connect to the aggregated MCP endpoint through the Cline UI or by editing `cline_mcp_settings.json`. + +==== Enable MCP mode + +. Open VS Code Settings (Cmd/Ctrl + ,) +. Search for "Cline > Mcp: Mode" +. Enable the MCP mode toggle + +==== Configure MCP server via Cline UI + +. Open the Cline extension panel in VS Code +. Navigate to MCP server settings +. Add the Redpanda AI Gateway MCP server with the connection details + +==== Configure via cline_mcp_settings.json + +Alternatively, edit `cline_mcp_settings.json` (located in the Cline extension storage directory): [source,json] ---- { - "cline.mcpServers": { + "mcpServers": { "redpanda-ai-gateway": { - "transport": "http", + "type": "streamableHttp", "url": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/mcp", "headers": { "Authorization": "Bearer YOUR_API_TOKEN", @@ -383,30 +386,22 @@ Configure Cline to connect to the aggregated MCP endpoint: } ---- -This configuration: - -* Connects Cline to the aggregated MCP endpoint -* Routes LLM requests through the AI Gateway -* Includes authentication and gateway identification headers +Replace: -=== User settings vs workspace settings +* `{CLUSTER_ID}`: Your Redpanda cluster ID +* `YOUR_API_TOKEN`: The API token generated earlier +* `GATEWAY_ID`: The gateway ID from gateway creation -Cline settings can be configured at two levels: +This configuration connects Cline to the aggregated MCP endpoint with authentication and gateway identification headers. -[cols="1,2,2"] -|=== -|Level |Location |Use Case +=== Configuration scope -|User settings -|`~/.vscode/settings.json` -|Personal API token, default gateway for all projects +Cline stores configuration in the extension's global state: -|Workspace settings -|`.vscode/settings.json` in project -|Project-specific gateway, shared team configuration -|=== +* *API Provider settings*: Stored globally per VS Code instance, applies to all workspaces +* *MCP server settings*: Can be configured per workspace using `cline_mcp_settings.json` -Use workspace settings when different projects require different gateways (for example, development vs production environments). +For project-specific MCP server configurations (for example, development vs production gateways), place `cline_mcp_settings.json` in the workspace directory and configure different MCP servers per project. == Monitor Cline usage From d73e5721aa07a675399effa7bde524a6bbfc240a Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:49:27 -0700 Subject: [PATCH 43/97] Replace hardcoded token with env var in curl example Replace "YOUR_API_TOKEN" with "${REDPANDA_API_TOKEN}" environment variable in the curl Authorization header to avoid secret scanner false positives. Add inline comment and NOTE block to inform users they must set the environment variable before running the command. Co-Authored-By: Claude Sonnet 4.5 --- .../ai-agents/pages/ai-gateway/integrations/cline-admin.adoc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc index 64fb52b7c..8552199c4 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc @@ -455,8 +455,9 @@ Programmatically access logs for integration with monitoring systems: [source,bash] ---- +# Set REDPANDA_API_TOKEN environment variable before running curl https://{CLUSTER_ID}.cloud.redpanda.com/api/ai-gateway/logs \ - -H "Authorization: Bearer YOUR_API_TOKEN" \ + -H "Authorization: Bearer ${REDPANDA_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "gateway_id": "GATEWAY_ID", @@ -466,6 +467,8 @@ curl https://{CLUSTER_ID}.cloud.redpanda.com/api/ai-gateway/logs \ }' ---- +NOTE: Set the `REDPANDA_API_TOKEN` environment variable to your API token before running this command. + == Security considerations Apply these security best practices for Cline deployments. From 1b32ae01fe02f3a32dfc1ef5b3c49b82d250ad65 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:51:03 -0700 Subject: [PATCH 44/97] Update Continue.dev config to current YAML standard Convert configuration from JSON to YAML format: - Change file name from ~/.continue/config.json to ~/.continue/config.yaml - Convert all JSON examples to YAML syntax - Update models, tabAutocompleteModel, apiBase, apiKey references Update MCP configuration to official schema: - Change modelContextProtocolServers (plural) to modelContextProtocolServer (singular) - Replace transport type "http" with "streamable-http" (valid value) - Add recommended directory-based configuration using ~/.continue/mcpServers/ - Provide alternative inline configuration in config.yaml Document that Continue.dev automatically discovers MCP server configurations in the mcpServers/ directory, which is the preferred configuration method. Co-Authored-By: Claude Sonnet 4.5 --- .../integrations/continue-admin.adoc | 158 +++++++++--------- 1 file changed, 80 insertions(+), 78 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index f4f885d6a..015e07862 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -421,69 +421,54 @@ Provide these instructions to users configuring Continue.dev in their IDE. === Configuration file location -Continue.dev uses a JSON configuration file: +Continue.dev uses a YAML configuration file: -* VS Code: `~/.continue/config.json` -* JetBrains: `~/.continue/config.json` +* VS Code: `~/.continue/config.yaml` +* JetBrains: `~/.continue/config.yaml` === Multi-provider configuration Users configure Continue.dev with separate provider entries for each backend: -[source,json] +[source,yaml] ---- -{ - "models": [ - { - "title": "Claude Sonnet (Redpanda)", - "provider": "anthropic", - "model": "claude-sonnet-4-5", - "apiBase": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/anthropic", - "apiKey": "YOUR_API_TOKEN", - "requestOptions": { - "headers": { - "rp-aigw-id": "GATEWAY_ID" - } - } - }, - { - "title": "GPT-4o (Redpanda)", - "provider": "openai", - "model": "gpt-4o", - "apiBase": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/openai", - "apiKey": "YOUR_API_TOKEN", - "requestOptions": { - "headers": { - "rp-aigw-id": "GATEWAY_ID" - } - } - }, - { - "title": "GPT-4o-mini (Autocomplete)", - "provider": "openai", - "model": "gpt-4o-mini", - "apiBase": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/openai", - "apiKey": "YOUR_API_TOKEN", - "requestOptions": { - "headers": { - "rp-aigw-id": "GATEWAY_ID" - } - } - } - ], - "tabAutocompleteModel": { - "title": "GPT-4o-mini (Autocomplete)", - "provider": "openai", - "model": "gpt-4o-mini", - "apiBase": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/openai", - "apiKey": "YOUR_API_TOKEN", - "requestOptions": { - "headers": { - "rp-aigw-id": "GATEWAY_ID" - } - } - } -} +models: + - title: Claude Sonnet (Redpanda) + provider: anthropic + model: claude-sonnet-4-5 + apiBase: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/anthropic + apiKey: YOUR_API_TOKEN + requestOptions: + headers: + rp-aigw-id: GATEWAY_ID + + - title: GPT-4o (Redpanda) + provider: openai + model: gpt-4o + apiBase: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/openai + apiKey: YOUR_API_TOKEN + requestOptions: + headers: + rp-aigw-id: GATEWAY_ID + + - title: GPT-4o-mini (Autocomplete) + provider: openai + model: gpt-4o-mini + apiBase: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/openai + apiKey: YOUR_API_TOKEN + requestOptions: + headers: + rp-aigw-id: GATEWAY_ID + +tabAutocompleteModel: + title: GPT-4o-mini (Autocomplete) + provider: openai + model: gpt-4o-mini + apiBase: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/v1/openai + apiKey: YOUR_API_TOKEN + requestOptions: + headers: + rp-aigw-id: GATEWAY_ID ---- Replace: @@ -494,33 +479,50 @@ Replace: === MCP server configuration -Configure Continue.dev to connect to the aggregated MCP endpoint: +Configure Continue.dev to connect to the aggregated MCP endpoint. -[source,json] +==== Recommended: Directory-based configuration + +The preferred method is to create MCP server configuration files in the `~/.continue/mcpServers/` directory: + +. Create the directory: `mkdir -p ~/.continue/mcpServers` +. Create `~/.continue/mcpServers/redpanda-ai-gateway.yaml`: ++ +[source,yaml] +---- +transport: + type: streamable-http + url: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/mcp + headers: + Authorization: Bearer YOUR_API_TOKEN + rp-aigw-id: GATEWAY_ID +---- + +Continue.dev automatically discovers MCP server configurations in this directory. + +==== Alternative: Inline configuration + +Alternatively, embed MCP server configuration in `~/.continue/config.yaml`: + +[source,yaml] ---- -{ - "experimental": { - "modelContextProtocolServers": [ - { - "transport": { - "type": "http", - "url": "https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/mcp", - "headers": { - "Authorization": "Bearer YOUR_API_TOKEN", - "rp-aigw-id": "GATEWAY_ID" - } - } - } - ] - } -} +experimental: + modelContextProtocolServer: + transport: + type: streamable-http + url: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/mcp + headers: + Authorization: Bearer YOUR_API_TOKEN + rp-aigw-id: GATEWAY_ID ---- -This configuration: +Replace: + +* `{CLUSTER_ID}`: Your Redpanda cluster ID +* `YOUR_API_TOKEN`: The API token generated earlier +* `GATEWAY_ID`: The gateway ID from gateway creation -* Connects Continue.dev to the aggregated MCP endpoint -* Routes LLM requests through provider-specific backends -* Includes authentication and gateway identification headers +This configuration connects Continue.dev to the aggregated MCP endpoint with authentication and gateway identification headers. === Model selection strategy From c34b122107eb7f165f5e328c4d1e61d480c68aff Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:52:26 -0700 Subject: [PATCH 45/97] Fix Continue.dev environment variable interpolation syntax Replace legacy JSON config with YAML using correct secrets interpolation: - Change from ${VAR} to ${{ secrets.VAR }} syntax - Update models -> apiKey, apiBase with secrets.REDPANDA_API_KEY and secrets.REDPANDA_GATEWAY_URL - Update requestOptions.headers.rp-aigw-id with secrets.REDPANDA_GATEWAY_ID - Update experimental.modelContextProtocolServer fields (changed from plural to singular per schema) - Change transport type from "http" to "streamable-http" - Replace transport.url, Authorization header, and rp-aigw-id header with secrets interpolation Add IMPORTANT note that ${VAR} syntax from config.json is not supported and will be treated as literal strings in Continue.dev. Replace shell environment variable exports with instructions to set secrets in Continue.dev settings UI. Co-Authored-By: Claude Sonnet 4.5 --- .../integrations/continue-user.adoc | 76 +++++++------------ 1 file changed, 29 insertions(+), 47 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index 2b1022fd5..577b7250d 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -268,59 +268,41 @@ If using deferred tool loading in your gateway, you'll see a search tool and MCP == Configure with environment variables -For sensitive credentials or multi-environment setups, use environment variables: +For sensitive credentials or multi-environment setups, use Continue.dev's secrets interpolation in `config.yaml`: -[,json] +[,yaml] ---- -{ - "models": [ - { - "title": "Gateway - Claude Sonnet", - "provider": "anthropic", - "model": "claude-sonnet-4-5", - "apiKey": "${REDPANDA_API_KEY}", - "apiBase": "${REDPANDA_GATEWAY_URL}", - "requestOptions": { - "headers": { - "rp-aigw-id": "${REDPANDA_GATEWAY_ID}" - } - } - } - ], - "experimental": { - "modelContextProtocolServers": [ - { - "transport": { - "type": "http", - "url": "${REDPANDA_GATEWAY_URL}/mcp", - "headers": { - "Authorization": "Bearer ${REDPANDA_API_KEY}", - "rp-aigw-id": "${REDPANDA_GATEWAY_ID}" - } - } - } - ] - } -} ----- - -Set environment variables before launching your editor: +models: + - title: Gateway - Claude Sonnet + provider: anthropic + model: claude-sonnet-4-5 + apiKey: ${{ secrets.REDPANDA_API_KEY }} + apiBase: ${{ secrets.REDPANDA_GATEWAY_URL }} + requestOptions: + headers: + rp-aigw-id: ${{ secrets.REDPANDA_GATEWAY_ID }} -[,bash] ----- -export REDPANDA_GATEWAY_URL="https://gw.ai.panda.com" -export REDPANDA_GATEWAY_ID="gateway-abc123" -export REDPANDA_API_KEY="your-api-key" +experimental: + modelContextProtocolServer: + transport: + type: streamable-http + url: ${{ secrets.REDPANDA_GATEWAY_URL }}/mcp + headers: + Authorization: Bearer ${{ secrets.REDPANDA_API_KEY }} + rp-aigw-id: ${{ secrets.REDPANDA_GATEWAY_ID }} ---- -On Windows (PowerShell): +IMPORTANT: Continue.dev uses the `${{ secrets.* }}` syntax for interpolation in `config.yaml`. The legacy `${VAR}` syntax from `config.json` is not supported and will be treated as a literal string. -[,powershell] ----- -$env:REDPANDA_GATEWAY_URL = "https://gw.ai.panda.com" -$env:REDPANDA_GATEWAY_ID = "gateway-abc123" -$env:REDPANDA_API_KEY = "your-api-key" ----- +Set secrets in Continue.dev settings: + +. Open Continue.dev settings in your IDE +. Navigate to the "Secrets" section +. Add the following secrets: ++ +* `REDPANDA_GATEWAY_URL`: `https://gw.ai.panda.com` +* `REDPANDA_GATEWAY_ID`: `gateway-abc123` +* `REDPANDA_API_KEY`: `your-api-key` == Project-level configuration From 48a108b5db9f281289271c7c53747af49dc936c9 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:53:27 -0700 Subject: [PATCH 46/97] Fix Cursor settings.json environment variable handling Add IMPORTANT note that VS Code settings.json does not support ${VAR} interpolation - such placeholders are treated as literal strings. Provide two alternatives for handling sensitive credentials: Option 1: Script-based generation - Add bash script that reads environment variables and generates settings.json with actual values for cursor.overrideOpenAIBaseUrl, cursor.overrideOpenAIApiKey, and openai.additionalHeaders - Add PowerShell script for Windows users with ConvertTo-Json - Include instructions to run script before launching Cursor Option 2: Manual value replacement - Show example with concrete placeholder values users must replace - Add security note about file permissions (chmod 600) Remove misleading instructions to set environment variables and launch from terminal, as this does not enable interpolation in settings.json. Co-Authored-By: Claude Sonnet 4.5 --- .../ai-gateway/integrations/cursor-user.adoc | 70 +++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc index 24460dc55..45041ea5d 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc @@ -192,12 +192,26 @@ Workspace settings override global settings. Use this to: === Configuration with environment variables -For sensitive credentials, use environment variables instead of hardcoding values. +For sensitive credentials, avoid hardcoding values in `settings.json`. -In `settings.json`: +IMPORTANT: VS Code `settings.json` does not support `${VAR}` interpolation - such placeholders will be treated as literal strings. To use environment variables, generate the settings file dynamically with a script. -[,json] +==== Option 1: Generate settings.json with a script + +Create a setup script that reads environment variables and writes the actual values to `settings.json`: + +[,bash] ---- +#!/bin/bash +# setup-cursor-config.sh + +# Set your credentials +export REDPANDA_GATEWAY_URL="https://gw.ai.panda.com" +export REDPANDA_GATEWAY_ID="gateway-abc123" +export REDPANDA_API_KEY="your-api-key" + +# Generate settings.json +cat > ~/.cursor/settings.json < Date: Fri, 23 Jan 2026 17:54:51 -0700 Subject: [PATCH 47/97] Fix GitHub Copilot configuration settings and flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update Option 1 (built-in custom models): - Change github.copilot.advanced.customModels to correct setting: github.copilot.chat.customOAIModels (experimental chat OAI models) - Restructure configuration as array instead of nested object - Add IMPORTANT note that API keys/headers must be configured via Copilot Chat UI, not settings.json - Replace environment variable instructions with Copilot Chat → Manage Models UI flow: Add Model → Select provider → Enter API key and custom headers (rp-aigw-id) - Remove references to non-working environment variable approach Update Option 2 (OAI Compatible Provider extension): - Replace incorrect oai.provider.* settings with actual extension settings: oaicopilot.baseUrl and oaicopilot.models - Remove non-existent useOAIProvider setting from configuration - Add IMPORTANT note that API keys/headers are NOT set in settings.json - Document correct flow: Copilot Chat → Manage Models → Configure provider → Enter API key and custom headers - Remove environment variable instructions that don't apply - Remove unnecessary window reload step Both options now correctly document that users must use the Copilot Chat → Manage Models UI to configure API keys and custom headers. Co-Authored-By: Claude Sonnet 4.5 --- .../integrations/github-copilot-user.adoc | 162 +++++++----------- 1 file changed, 58 insertions(+), 104 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc index aa3aa8de4..b52f79d3b 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc @@ -86,81 +86,58 @@ This approach configures VS Code to recognize your AI Gateway as a custom OpenAI . Open VS Code Settings: ** macOS: `Cmd+,` ** Windows/Linux: `Ctrl+,` -. Search for `github.copilot.advanced` +. Search for `github.copilot.chat.customOAIModels` . Click *Edit in settings.json* . Add the following configuration: [,json] ---- { - "github.copilot.advanced": { - "customModels": { - "redpanda-gateway": { - "endpoint": "https://gw.ai.panda.com/v1", - "apiKey": "${env:REDPANDA_API_KEY}", - "models": [ - { - "id": "anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5 (Gateway)", - "type": "chat" - }, - { - "id": "openai/gpt-4o", - "name": "GPT-4o (Gateway)", - "type": "chat" - }, - { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o Mini (Gateway)", - "type": "completion" - } - ] - } + "github.copilot.chat.customOAIModels": [ + { + "id": "anthropic/claude-sonnet-4-5", + "name": "Claude Sonnet 4.5 (Gateway)", + "endpoint": "https://gw.ai.panda.com/v1", + "provider": "redpanda-gateway" + }, + { + "id": "openai/gpt-4o", + "name": "GPT-4o (Gateway)", + "endpoint": "https://gw.ai.panda.com/v1", + "provider": "redpanda-gateway" } - } + ] } ---- Replace `https://gw.ai.panda.com/v1` with your gateway endpoint. -==== Add gateway ID header - -The custom models configuration doesn't support custom headers directly. To add the `rp-aigw-id` header, use one of these approaches: +IMPORTANT: This experimental feature requires configuring API keys and custom headers through the Copilot Chat UI, not in `settings.json`. -**Approach A: Use OAI Compatible Provider extension** (recommended, see Option 2 below) +==== Configure API key and headers via Copilot Chat UI -**Approach B: Configure gateway to use API key for routing** (if your gateway supports this) - -Check your AI Gateway documentation to see if you can embed the gateway ID in the API key or use a different authentication method that doesn't require custom headers. - -==== Set environment variable - -Set the API key as an environment variable before launching VS Code: - -[,bash] ----- -export REDPANDA_API_KEY="your-api-key" -code . ----- - -On Windows (PowerShell): - -[,powershell] ----- -$env:REDPANDA_API_KEY = "your-api-key" -code . ----- +. Open Copilot Chat in VS Code (`Cmd+I` or `Ctrl+I`) +. Click the model selector dropdown +. Click *Manage Models* at the bottom of the dropdown +. Click *Add Model* +. Select your configured provider ("redpanda-gateway") +. Enter the connection details: +** *Base URL*: `https://gw.ai.panda.com/v1` (should match your settings.json endpoint) +** *API Key*: Your Redpanda API key +** *Custom Headers*: Click *Add Header* +*** Header name: `rp-aigw-id` +*** Header value: `gateway-abc123` (your gateway ID) +. Click *Save* ==== Select model -. Open a file in VS Code . Open Copilot chat with `Cmd+I` (macOS) or `Ctrl+I` (Windows/Linux) . Click the model selector dropdown . Choose a model from the "redpanda-gateway" provider === Option 2: OAI Compatible Provider extension -The OAI Compatible Provider extension simplifies custom provider configuration and supports custom headers. +The OAI Compatible Provider extension provides enhanced support for OpenAI-compatible endpoints with custom headers. ==== Install extension @@ -168,73 +145,50 @@ The OAI Compatible Provider extension simplifies custom provider configuration a . Search for "OAI Compatible Provider" . Click *Install* -==== Configure provider +==== Configure base URL in settings + +Add the base URL configuration in VS Code settings: . Open VS Code Settings (`Cmd+,` or `Ctrl+,`) -. Search for `oai.provider` -. Configure the following settings: +. Search for `oaicopilot` +. Click *Edit in settings.json* +. Add the following: [,json] ---- { - "oai.provider.endpoint": "https://gw.ai.panda.com/v1", - "oai.provider.apiKey": "${env:REDPANDA_API_KEY}", - "oai.provider.headers": { - "rp-aigw-id": "gateway-abc123" - }, - "oai.provider.models": [ - { - "id": "anthropic/claude-sonnet-4-5", - "name": "Claude Sonnet 4.5", - "type": "chat" - }, - { - "id": "openai/gpt-4o-mini", - "name": "GPT-4o Mini", - "type": "completion" - } + "oaicopilot.baseUrl": "https://gw.ai.panda.com/v1", + "oaicopilot.models": [ + "anthropic/claude-sonnet-4-5", + "openai/gpt-4o", + "openai/gpt-4o-mini" ] } ---- -Replace placeholder values: - -* `https://gw.ai.panda.com/v1` - Your gateway endpoint -* `gateway-abc123` - Your gateway ID - -==== Enable for Copilot - -. Search for `github.copilot.advanced` in settings -. Add the following: - -[,json] ----- -{ - "github.copilot.advanced": { - "useOAIProvider": true - } -} ----- +Replace `https://gw.ai.panda.com/v1` with your gateway endpoint. -. Reload VS Code window: -** Command Palette (`Cmd+Shift+P` or `Ctrl+Shift+P`) -** Type "Developer: Reload Window" +==== Configure API key and headers via Copilot Chat UI -==== Set environment variable +IMPORTANT: Do not configure API keys or custom headers in `settings.json`. Use the Copilot Chat UI instead. -[,bash] ----- -export REDPANDA_API_KEY="your-api-key" -code . ----- +. Open Copilot Chat in VS Code (`Cmd+I` or `Ctrl+I`) +. Click the model selector dropdown +. Click *Manage Models* +. Find the OAI Compatible Provider in the list +. Click *Configure* or *Edit* +. Enter the connection details: +** *API Key*: Your Redpanda API key +** *Custom Headers*: Add the `rp-aigw-id` header +*** Header name: `rp-aigw-id` +*** Header value: `gateway-abc123` (your gateway ID) +. Click *Save* -On Windows (PowerShell): +==== Select model -[,powershell] ----- -$env:REDPANDA_API_KEY = "your-api-key" -code . ----- +. Open Copilot chat with `Cmd+I` (macOS) or `Ctrl+I` (Windows/Linux) +. Click the model selector dropdown +. Choose a model from the OAI Compatible Provider == Configure in JetBrains IDEs From a034c9c2fb6f0504505932f35622287870b8bb3c Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 17:55:31 -0700 Subject: [PATCH 48/97] Remove duplicate rp-aigw-id from LlamaIndex example Remove rp-aigw-id from additional_kwargs in the LlamaIndex OpenAI client configuration. The header was being set twice - once in additional_kwargs (which goes in the request body) and once in default_headers (which sets HTTP headers). Keep only the default_headers configuration to correctly set the rp-aigw-id as an HTTP header. Co-Authored-By: Claude Sonnet 4.5 --- modules/ai-agents/pages/ai-gateway/migration-guide.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc index d80d0c373..3c5c228d4 100644 --- a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/migration-guide.adoc @@ -630,7 +630,6 @@ if use_gateway: model="openai/gpt-4o", api_base=os.getenv("REDPANDA_AI_GATEWAY_URL"), api_key=os.getenv("REDPANDA_AI_GATEWAY_TOKEN"), - additional_kwargs={"headers": {"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")}}, default_headers={"rp-aigw-id": os.getenv("REDPANDA_AI_GATEWAY_ID")} ) else: From 930b261783f5021d34eb527663156c94cc1aa90e Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 18:29:54 -0700 Subject: [PATCH 49/97] Fix Continue.dev config file references to use YAML Update security guidance section to reference config.yaml instead of config.json: - Change "stores the API token in plain text in config.json" to config.yaml - Update version control warning to reference config.yaml - Update chmod example from ~/.continue/config.json to ~/.continue/config.yaml Keep guidance points the same: never commit the config file, restrict filesystem permissions, and rotate tokens if compromised. Co-Authored-By: Claude Sonnet 4.5 --- .../pages/ai-gateway/integrations/continue-admin.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index 015e07862..7b69cf291 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -656,10 +656,10 @@ Review which MCP tools Continue.dev clients can access: === Protect API keys in configuration -Continue.dev stores the API token in plain text in `config.json`. Remind users to: +Continue.dev stores the API token in plain text in `config.yaml`. Remind users to: -* Never commit `config.json` to version control -* Use file system permissions to restrict access (for example, `chmod 600 ~/.continue/config.json`) +* Never commit `config.yaml` to version control +* Use file system permissions to restrict access (for example, `chmod 600 ~/.continue/config.yaml`) * Rotate tokens if they suspect compromise == Troubleshooting From 3a4d0267e0835497ea514791f6099bde36203dab Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 19:27:49 -0700 Subject: [PATCH 50/97] Fix Continue.dev MCP server schema in config.yaml examples Update Continue.dev documentation to use the correct MCP server configuration schema: - Change from experimental.modelContextProtocolServer (singular) to mcpServers (plural) at root level - Add array syntax with dash (-) before transport configuration - Update continue-user.adoc inline config.yaml examples (2 instances) - Update continue-admin.adoc inline config.yaml example - Fix troubleshooting references to use new mcpServers schema - Convert JSON config example to YAML with new schema for consistency The new schema allows multiple MCP servers to be configured and aligns with Continue.dev's current config.yaml structure. Directory-based configuration in ~/.continue/mcpServers/ already uses correct schema. Co-Authored-By: Claude Sonnet 4.5 --- .../integrations/continue-admin.adoc | 7 +-- .../integrations/continue-user.adoc | 58 +++++++------------ 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index 7b69cf291..8585ab2d9 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -506,9 +506,8 @@ Alternatively, embed MCP server configuration in `~/.continue/config.yaml`: [source,yaml] ---- -experimental: - modelContextProtocolServer: - transport: +mcpServers: + - transport: type: streamable-http url: https://{CLUSTER_ID}.cloud.redpanda.com/ai-gateway/mcp headers: @@ -715,7 +714,7 @@ Symptom: Continue.dev does not discover MCP tools. Causes and solutions: -* **MCP configuration missing**: Ensure `experimental.modelContextProtocolServers` is configured +* **MCP configuration missing**: Ensure `mcpServers` is configured * **MCP servers not configured in gateway**: Add MCP server endpoints in the gateway's MCP tab * **Deferred loading enabled but search failing**: Check that the search tool is correctly configured * **MCP server authentication failing**: Verify MCP server authentication credentials in the gateway configuration diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index 577b7250d..930db05c5 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -222,40 +222,27 @@ When using OpenAI provider format: Connect Continue.dev to your AI Gateway's MCP endpoint to aggregate tools from multiple MCP servers. -Add the `experimental` section to `config.json`: +Add the MCP configuration to `config.yaml`: -[,json] +[,yaml] ---- -{ - "models": [ - { - "title": "Gateway - Claude Sonnet", - "provider": "anthropic", - "model": "claude-sonnet-4-5", - "apiKey": "YOUR_REDPANDA_API_KEY", - "apiBase": "https://gw.ai.panda.com", - "requestOptions": { - "headers": { - "rp-aigw-id": "GATEWAY_ID" - } - } - } - ], - "experimental": { - "modelContextProtocolServers": [ - { - "transport": { - "type": "http", - "url": "https://gw.ai.panda.com/mcp", - "headers": { - "Authorization": "Bearer YOUR_REDPANDA_API_KEY", - "rp-aigw-id": "GATEWAY_ID" - } - } - } - ] - } -} +models: + - title: Gateway - Claude Sonnet + provider: anthropic + model: claude-sonnet-4-5 + apiKey: YOUR_REDPANDA_API_KEY + apiBase: https://gw.ai.panda.com + requestOptions: + headers: + rp-aigw-id: GATEWAY_ID + +mcpServers: + - transport: + type: streamable-http + url: https://gw.ai.panda.com/mcp + headers: + Authorization: Bearer YOUR_REDPANDA_API_KEY + rp-aigw-id: GATEWAY_ID ---- After adding this configuration: @@ -282,9 +269,8 @@ models: headers: rp-aigw-id: ${{ secrets.REDPANDA_GATEWAY_ID }} -experimental: - modelContextProtocolServer: - transport: +mcpServers: + - transport: type: streamable-http url: ${{ secrets.REDPANDA_GATEWAY_URL }}/mcp headers: @@ -667,7 +653,7 @@ Check Continue.dev settings in your editor: . **MCP configuration missing** + -Verify the `experimental.modelContextProtocolServers` section exists in `config.json`. +Verify the `mcpServers` section exists in `config.yaml`. . **Incorrect MCP endpoint** + From d0fdb50fa9e8515490fef4eb6fb72579142f5eba Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 19:28:52 -0700 Subject: [PATCH 51/97] Fix unsupported ${VAR} interpolation in Continue.dev project config Replace misleading ${VAR} syntax in .continuerc.json example with explicit placeholder text (your_project_api_key_here, etc.) to avoid confusion. Add IMPORTANT note explaining that .continuerc.json does not support environment variable interpolation and must use hardcoded values. Direct users to config.yaml with ${{ secrets.* }} syntax or config.ts for programmatic environment access as alternatives. Co-Authored-By: Claude Sonnet 4.5 --- .../pages/ai-gateway/integrations/continue-user.adoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index 930db05c5..b58df3acd 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -302,11 +302,11 @@ Override global settings for specific projects by creating `.continuerc.json` in "title": "Project Gateway - Claude Haiku", "provider": "anthropic", "model": "claude-haiku", - "apiKey": "${PROJECT_API_KEY}", + "apiKey": "your_project_api_key_here", "apiBase": "https://gw.project.ai.panda.com", "requestOptions": { "headers": { - "rp-aigw-id": "${PROJECT_GATEWAY_ID}" + "rp-aigw-id": "your_project_gateway_id_here" } } } @@ -314,6 +314,8 @@ Override global settings for specific projects by creating `.continuerc.json` in } ---- +IMPORTANT: `.continuerc.json` does not support environment variable interpolation. You must hardcode values in this file. For dynamic configuration, use `~/.continue/config.yaml` with `${{ secrets.* }}` syntax (see <>) or create a `~/.continue/config.ts` file for programmatic environment access. + Project-level configuration takes precedence over global configuration. Use this to: * Route different projects through different gateways From ac4d51db6142ba2883cd5fe17a45e08227cd9034 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 19:39:43 -0700 Subject: [PATCH 52/97] Improve Continue.dev documentation clarity and compliance Address critical documentation issues identified in audit: 1. Remove checkboxes from learning objectives in both user and admin guides per content architecture standards 2. Add config file format clarification section explaining JSON vs YAML, when each is required/recommended, helping users understand which format to choose 3. Fix environment variable syntax documentation: - Add explicit anchor [[configure-env-vars]] for cross-references - Clarify ${{ secrets.* }} is YAML-only - Explain config.json does not support any variable interpolation - Clarify ${VAR} syntax is treated as literal string 4. Add security warning in admin guide about hardcoded tokens with cross-reference to environment variable interpolation best practices 5. Update admin guide to acknowledge both JSON and YAML formats are supported, explaining why YAML is recommended for MCP features These changes eliminate confusion about config file formats and improve security guidance for production deployments. Co-Authored-By: Claude Sonnet 4.5 --- .../integrations/continue-admin.adoc | 12 ++++-- .../integrations/continue-user.adoc | 43 +++++++++++++++---- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index 8585ab2d9..defbbd05e 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Continue.dev clients accessing multiple After reading this page, you will be able to: -* [ ] Configure AI Gateway endpoints for Continue.dev connectivity. -* [ ] Set up multi-provider backends with native format routing. -* [ ] Deploy MCP tool aggregation for Continue.dev tool discovery. +* Configure AI Gateway endpoints for Continue.dev connectivity +* Set up multi-provider backends with native format routing +* Deploy MCP tool aggregation for Continue.dev tool discovery == Prerequisites @@ -421,11 +421,13 @@ Provide these instructions to users configuring Continue.dev in their IDE. === Configuration file location -Continue.dev uses a YAML configuration file: +Continue.dev supports both JSON and YAML configuration formats. This guide uses YAML (`config.yaml`) because it supports MCP server configuration and environment variable interpolation: * VS Code: `~/.continue/config.yaml` * JetBrains: `~/.continue/config.yaml` +NOTE: While `config.json` is still supported for basic LLM configuration, `config.yaml` is required for MCP server integration. + === Multi-provider configuration Users configure Continue.dev with separate provider entries for each backend: @@ -497,6 +499,8 @@ transport: Authorization: Bearer YOUR_API_TOKEN rp-aigw-id: GATEWAY_ID ---- ++ +IMPORTANT: For production deployments, use environment variable interpolation with `${{ secrets.VARIABLE }}` syntax instead of hardcoding tokens. See xref:ai-agents:ai-gateway/integrations/continue-user.adoc#configure-env-vars[Configure with environment variables] in the user guide for details. Continue.dev automatically discovers MCP server configurations in this directory. diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index b58df3acd..ec844ce93 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* [ ] Configure Continue.dev to connect to AI Gateway for chat and autocomplete. -* [ ] Set up MCP server integration through AI Gateway. -* [ ] Optimize Continue.dev settings for cost and performance. +* Configure Continue.dev to connect to AI Gateway for chat and autocomplete +* Set up MCP server integration through AI Gateway +* Optimize Continue.dev settings for cost and performance == Prerequisites @@ -43,12 +43,17 @@ Continue.dev is an open-source AI coding assistant that integrates with VS Code By routing Continue.dev through AI Gateway, you gain centralized observability, cost controls, and the ability to aggregate multiple MCP servers into a single interface. -== Configuration file location +== Configuration files -Continue.dev stores configuration in `config.json`: +Continue.dev supports two configuration file formats: -* VS Code: `~/.continue/config.json` -* JetBrains: `~/.continue/config.json` (same location) +* `config.json` (legacy format) +* `config.yaml` (recommended format) + +Both files are stored in the same location: + +* VS Code: `~/.continue/` +* JetBrains: `~/.continue/` Create the directory if it doesn't exist: @@ -57,6 +62,23 @@ Create the directory if it doesn't exist: mkdir -p ~/.continue ---- +=== Choose a configuration format + +[cols="1,2,2"] +|=== +|Format |Use when |Limitations + +|`config.json` +|You need basic LLM configuration without MCP servers +|Does not support MCP server configuration or environment variable interpolation + +|`config.yaml` +|You need MCP server integration or environment variable interpolation +|Requires Continue.dev version that supports YAML (recent versions) +|=== + +TIP: Use `config.yaml` for new setups to take advantage of MCP server integration and the `${{ secrets.* }}` environment variable syntax. + == Basic configuration Create or edit `~/.continue/config.json` with the following structure to connect to AI Gateway: @@ -253,9 +275,12 @@ After adding this configuration: If using deferred tool loading in your gateway, you'll see a search tool and MCP orchestrator tool instead of all tools upfront. +[[configure-env-vars]] == Configure with environment variables -For sensitive credentials or multi-environment setups, use Continue.dev's secrets interpolation in `config.yaml`: +For sensitive credentials or multi-environment setups, use Continue.dev's secrets interpolation in `config.yaml`. + +IMPORTANT: Environment variable interpolation is only supported in `config.yaml` files. The `config.json` format does not support any form of variable substitution - all values must be hardcoded. [,yaml] ---- @@ -278,7 +303,7 @@ mcpServers: rp-aigw-id: ${{ secrets.REDPANDA_GATEWAY_ID }} ---- -IMPORTANT: Continue.dev uses the `${{ secrets.* }}` syntax for interpolation in `config.yaml`. The legacy `${VAR}` syntax from `config.json` is not supported and will be treated as a literal string. +IMPORTANT: Continue.dev uses the `${{ secrets.* }}` syntax for interpolation in `config.yaml`. Do not use the `${VAR}` shell syntax - Continue.dev treats it as a literal string rather than performing substitution. Set secrets in Continue.dev settings: From f754065d0a9eb4673f5408dc7b5f1658cb69a0f6 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 19:41:00 -0700 Subject: [PATCH 53/97] Remove checkboxes and periods from learning objectives across all integration files Apply content architecture standards to all AI Gateway integration documentation: - Remove [ ] checkbox formatting from learning objectives - Remove trailing periods from objective fragments Files updated: - cursor-user.adoc, cursor-admin.adoc - github-copilot-user.adoc, github-copilot-admin.adoc - claude-code-user.adoc, claude-code-admin.adoc - cline-user.adoc, cline-admin.adoc Learning objectives should be simple list items without interactive elements or punctuation for fragments. Co-Authored-By: Claude Sonnet 4.5 --- .../pages/ai-gateway/integrations/claude-code-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/claude-code-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/cline-admin.adoc | 6 +++--- .../ai-agents/pages/ai-gateway/integrations/cline-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/cursor-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/cursor-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/github-copilot-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/github-copilot-user.adoc | 6 +++--- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc index 5bbb2e844..8c8272f10 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Claude Code clients accessing LLM provi After reading this page, you will be able to: -* [ ] Configure AI Gateway endpoints for Claude Code connectivity. -* [ ] Set up authentication and access control for Claude Code clients. -* [ ] Deploy MCP tool aggregation for Claude Code tool discovery. +* Configure AI Gateway endpoints for Claude Code connectivity +* Set up authentication and access control for Claude Code clients +* Deploy MCP tool aggregation for Claude Code tool discovery == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc index 7fb085124..41abbf2b6 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* [ ] Configure Claude Code to connect to AI Gateway endpoints. -* [ ] Set up MCP server integration through AI Gateway. -* [ ] Verify Claude Code is routing requests through the gateway. +* Configure Claude Code to connect to AI Gateway endpoints +* Set up MCP server integration through AI Gateway +* Verify Claude Code is routing requests through the gateway == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc index 8552199c4..d3f6464d0 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Cline (formerly Claude Dev) clients acc After reading this page, you will be able to: -* [ ] Configure AI Gateway endpoints for Cline connectivity. -* [ ] Set up authentication and access control for Cline clients. -* [ ] Deploy MCP tool aggregation for Cline tool discovery. +* Configure AI Gateway endpoints for Cline connectivity +* Set up authentication and access control for Cline clients +* Deploy MCP tool aggregation for Cline tool discovery == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc index cc66ca5d7..db1bba90a 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* [ ] Configure Cline to connect to AI Gateway for LLM requests and MCP tools. -* [ ] Set up autonomous mode with custom instructions and browser integration. -* [ ] Verify Cline routes requests through the gateway and optimize for cost. +* Configure Cline to connect to AI Gateway for LLM requests and MCP tools +* Set up autonomous mode with custom instructions and browser integration +* Verify Cline routes requests through the gateway and optimize for cost == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc index 55f305500..797b71c10 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Cursor IDE clients accessing multiple L After reading this page, you will be able to: -* [ ] Configure AI Gateway endpoints for Cursor IDE connectivity. -* [ ] Set up OpenAI-compatible transforms for multi-provider routing. -* [ ] Deploy multi-tenant authentication strategies for Cursor clients. +* Configure AI Gateway endpoints for Cursor IDE connectivity +* Set up OpenAI-compatible transforms for multi-provider routing +* Deploy multi-tenant authentication strategies for Cursor clients == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc index 45041ea5d..c3fc57643 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* [ ] Configure Cursor IDE to route LLM requests through AI Gateway. -* [ ] Set up MCP server integration for tool access through the gateway. -* [ ] Optimize Cursor settings for multi-tenancy and cost control. +* Configure Cursor IDE to route LLM requests through AI Gateway +* Set up MCP server integration for tool access through the gateway +* Optimize Cursor settings for multi-tenancy and cost control == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc index fb2cf320f..126378e29 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support GitHub Copilot clients accessing multip After reading this page, you will be able to: -* [ ] Configure AI Gateway endpoints for GitHub Copilot connectivity. -* [ ] Deploy multi-tenant authentication strategies for Copilot clients. -* [ ] Set up model aliasing and BYOK routing for GitHub Copilot. +* Configure AI Gateway endpoints for GitHub Copilot connectivity +* Deploy multi-tenant authentication strategies for Copilot clients +* Set up model aliasing and BYOK routing for GitHub Copilot == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc index b52f79d3b..3f3db4c13 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* [ ] Configure GitHub Copilot in VS Code and JetBrains IDEs to route requests through AI Gateway. -* [ ] Set up multi-tenancy with gateway ID headers for cost tracking. -* [ ] Configure enterprise BYOK deployments for team-wide Copilot access. +* Configure GitHub Copilot in VS Code and JetBrains IDEs to route requests through AI Gateway +* Set up multi-tenancy with gateway ID headers for cost tracking +* Configure enterprise BYOK deployments for team-wide Copilot access == Prerequisites From 8ce3de86693570ce21289093abb548b7443f3fea Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 19:43:32 -0700 Subject: [PATCH 54/97] Revert "Remove checkboxes and periods from learning objectives across all integration files" This reverts commit f754065. Checkboxes [ ] ARE the doc team standard for learning objectives in this repository, as evidenced by the MCP documentation pages and other content. The audit agent was incorrect in recommending their removal. Restored checkboxes and periods to learning objectives in all integration files to match the established pattern: * [ ] Learning objective text. Examples of this pattern in the repo: - modules/ai-agents/pages/mcp/remote/quickstart.adoc - modules/ai-agents/pages/mcp/remote/concepts.adoc - modules/ai-agents/pages/mcp/local/configuration.adoc --- .../pages/ai-gateway/integrations/claude-code-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/claude-code-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/cline-admin.adoc | 6 +++--- .../ai-agents/pages/ai-gateway/integrations/cline-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/continue-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/continue-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/cursor-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/cursor-user.adoc | 6 +++--- .../pages/ai-gateway/integrations/github-copilot-admin.adoc | 6 +++--- .../pages/ai-gateway/integrations/github-copilot-user.adoc | 6 +++--- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc index 8c8272f10..5bbb2e844 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Claude Code clients accessing LLM provi After reading this page, you will be able to: -* Configure AI Gateway endpoints for Claude Code connectivity -* Set up authentication and access control for Claude Code clients -* Deploy MCP tool aggregation for Claude Code tool discovery +* [ ] Configure AI Gateway endpoints for Claude Code connectivity. +* [ ] Set up authentication and access control for Claude Code clients. +* [ ] Deploy MCP tool aggregation for Claude Code tool discovery. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc index 41abbf2b6..7fb085124 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure Claude Code to connect to AI Gateway endpoints -* Set up MCP server integration through AI Gateway -* Verify Claude Code is routing requests through the gateway +* [ ] Configure Claude Code to connect to AI Gateway endpoints. +* [ ] Set up MCP server integration through AI Gateway. +* [ ] Verify Claude Code is routing requests through the gateway. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc index d3f6464d0..8552199c4 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Cline (formerly Claude Dev) clients acc After reading this page, you will be able to: -* Configure AI Gateway endpoints for Cline connectivity -* Set up authentication and access control for Cline clients -* Deploy MCP tool aggregation for Cline tool discovery +* [ ] Configure AI Gateway endpoints for Cline connectivity. +* [ ] Set up authentication and access control for Cline clients. +* [ ] Deploy MCP tool aggregation for Cline tool discovery. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc index db1bba90a..cc66ca5d7 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure Cline to connect to AI Gateway for LLM requests and MCP tools -* Set up autonomous mode with custom instructions and browser integration -* Verify Cline routes requests through the gateway and optimize for cost +* [ ] Configure Cline to connect to AI Gateway for LLM requests and MCP tools. +* [ ] Set up autonomous mode with custom instructions and browser integration. +* [ ] Verify Cline routes requests through the gateway and optimize for cost. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index defbbd05e..ce75b6306 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Continue.dev clients accessing multiple After reading this page, you will be able to: -* Configure AI Gateway endpoints for Continue.dev connectivity -* Set up multi-provider backends with native format routing -* Deploy MCP tool aggregation for Continue.dev tool discovery +* [ ] Configure AI Gateway endpoints for Continue.dev connectivity. +* [ ] Set up multi-provider backends with native format routing. +* [ ] Deploy MCP tool aggregation for Continue.dev tool discovery. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index ec844ce93..5dcb3094c 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure Continue.dev to connect to AI Gateway for chat and autocomplete -* Set up MCP server integration through AI Gateway -* Optimize Continue.dev settings for cost and performance +* [ ] Configure Continue.dev to connect to AI Gateway for chat and autocomplete. +* [ ] Set up MCP server integration through AI Gateway. +* [ ] Optimize Continue.dev settings for cost and performance. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc index 797b71c10..55f305500 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support Cursor IDE clients accessing multiple L After reading this page, you will be able to: -* Configure AI Gateway endpoints for Cursor IDE connectivity -* Set up OpenAI-compatible transforms for multi-provider routing -* Deploy multi-tenant authentication strategies for Cursor clients +* [ ] Configure AI Gateway endpoints for Cursor IDE connectivity. +* [ ] Set up OpenAI-compatible transforms for multi-provider routing. +* [ ] Deploy multi-tenant authentication strategies for Cursor clients. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc index c3fc57643..45041ea5d 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure Cursor IDE to route LLM requests through AI Gateway -* Set up MCP server integration for tool access through the gateway -* Optimize Cursor settings for multi-tenancy and cost control +* [ ] Configure Cursor IDE to route LLM requests through AI Gateway. +* [ ] Set up MCP server integration for tool access through the gateway. +* [ ] Optimize Cursor settings for multi-tenancy and cost control. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc index 126378e29..fb2cf320f 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc @@ -12,9 +12,9 @@ Configure Redpanda AI Gateway to support GitHub Copilot clients accessing multip After reading this page, you will be able to: -* Configure AI Gateway endpoints for GitHub Copilot connectivity -* Deploy multi-tenant authentication strategies for Copilot clients -* Set up model aliasing and BYOK routing for GitHub Copilot +* [ ] Configure AI Gateway endpoints for GitHub Copilot connectivity. +* [ ] Deploy multi-tenant authentication strategies for Copilot clients. +* [ ] Set up model aliasing and BYOK routing for GitHub Copilot. == Prerequisites diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc index 3f3db4c13..b52f79d3b 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc @@ -12,9 +12,9 @@ After xref:ai-agents:ai-gateway/gateway-quickstart.adoc[configuring your AI Gate After reading this page, you will be able to: -* Configure GitHub Copilot in VS Code and JetBrains IDEs to route requests through AI Gateway -* Set up multi-tenancy with gateway ID headers for cost tracking -* Configure enterprise BYOK deployments for team-wide Copilot access +* [ ] Configure GitHub Copilot in VS Code and JetBrains IDEs to route requests through AI Gateway. +* [ ] Set up multi-tenancy with gateway ID headers for cost tracking. +* [ ] Configure enterprise BYOK deployments for team-wide Copilot access. == Prerequisites From 44c45285f7e8d24329dac59dd2a0078a5c203d10 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Fri, 23 Jan 2026 20:02:30 -0700 Subject: [PATCH 55/97] update nav --- modules/ROOT/nav.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 519835c9e..fc1b92a15 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -38,7 +38,7 @@ *** Observability **** xref:ai-agents:ai-gateway/observability-logs.adoc[Request Logs] **** xref:ai-agents:ai-gateway/observability-metrics.adoc[Metrics and Analytics] -*** xref:ai-agents:ai-gateway/migration-guide.adoc[Migration Guide] +*** xref:ai-agents:ai-gateway/migration-guide.adoc[Migrate] *** xref:ai-agents:ai-gateway/integrations/index.adoc[Integrations] **** Claude Code ***** xref:ai-agents:ai-gateway/integrations/claude-code-admin.adoc[Admin Guide] From 6342ede36e95947fc07daea8116ff56ee2eb92e0 Mon Sep 17 00:00:00 2001 From: Kat Batuigas Date: Mon, 26 Jan 2026 12:43:49 -0800 Subject: [PATCH 56/97] Doc UI and update terminology for transcripts --- .../pages/observability/concepts.adoc | 62 ++--- .../pages/observability/view-transcripts.adoc | 220 ++++++++++++++++++ 2 files changed, 251 insertions(+), 31 deletions(-) create mode 100644 modules/ai-agents/pages/observability/view-transcripts.adoc diff --git a/modules/ai-agents/pages/observability/concepts.adoc b/modules/ai-agents/pages/observability/concepts.adoc index 99b0fa6a3..68464f033 100644 --- a/modules/ai-agents/pages/observability/concepts.adoc +++ b/modules/ai-agents/pages/observability/concepts.adoc @@ -1,12 +1,12 @@ = Transcripts and AI Observability -:description: Understand how Redpanda captures execution traces for agents and MCP servers using OpenTelemetry. +:description: Understand how Redpanda captures execution transcripts for agents and MCP servers using OpenTelemetry. :page-topic-type: concepts :personas: agent_developer, platform_admin, data_engineer -:learning-objective-1: Explain how traces and spans capture execution flow -:learning-objective-2: Interpret trace structure for debugging and monitoring -:learning-objective-3: Distinguish between observability traces and audit logs +:learning-objective-1: Explain how transcripts and spans capture execution flow +:learning-objective-2: Interpret transcript structure for debugging and monitoring +:learning-objective-3: Distinguish between transcripts and audit logs -Redpanda automatically captures execution traces for both AI agents and MCP servers, providing complete observability into how your agentic systems operate. +Redpanda automatically captures execution transcripts for both AI agents and MCP servers, providing complete observability into how your agentic systems operate. After reading this page, you will be able to: @@ -27,7 +27,7 @@ Transcripts capture: * Error conditions * Performance metrics -With 100% sampling, every operation is captured, creating complete transcripts that you can use for debugging, monitoring, and performance analysis. +With 100% sampling, every operation is captured, enabling comprehensive debugging, monitoring, and performance analysis. == Traces and spans @@ -37,13 +37,13 @@ OpenTelemetry traces provide a complete picture of how a request flows through y * A _span_ represents a single unit of work within that trace (such as a data processing operation or an external API call). * A trace contains one or more spans organized hierarchically, showing how operations relate to each other. -== Agent trace hierarchy +== Agent transcript hierarchy Agent executions create a hierarchy of spans that reflect how agents process requests. Understanding this hierarchy helps you interpret agent behavior and identify where issues occur. === Agent span types -Agent traces contain these span types: +Agent transcripts contain these span types: [cols="2,3,3", options="header"] |=== @@ -90,13 +90,13 @@ This shows: Examine span durations to identify where time is spent and optimize accordingly. -== MCP server trace hierarchy +== MCP server transcript hierarchy -MCP server executions create a different hierarchy that reflects tool invocations and internal processing. Understanding this hierarchy helps you debug tool execution and identify performance bottlenecks. +MCP server tool invocations produce a different span hierarchy focused on tool execution and internal processing. This structure reveals performance bottlenecks and helps debug tool-specific issues. === MCP server span types -MCP server traces contain these span types: +MCP server transcripts contain these span types: [cols="2,3,3", options="header"] |=== @@ -143,13 +143,13 @@ This shows: The majority of time (4+ seconds) is spent in tool execution, while internal processing (mapping) takes only microseconds. This indicates the tool itself (likely making external API calls or database queries) is the bottleneck, not Redpanda Connect's internal processing. -== Trace layers and scope +== Transcript layers and scope -Traces contain multiple layers of instrumentation, from HTTP transport through application logic to external service calls. The `scope.name` field in each span identifies which layer of instrumentation created that span. +Transcripts contain multiple layers of instrumentation, from HTTP transport through application logic to external service calls. The `scope.name` field in each span identifies which instrumentation layer created that span. === Instrumentation layers -A complete agent trace includes these layers: +A complete agent transcript includes these layers: [cols="2,2,4", options="header"] |=== @@ -178,7 +178,7 @@ A complete agent trace includes these layers: === How layers connect -Layers connect through parent-child relationships in a single trace: +Layers connect through parent-child relationships in a single transcript: ---- ai-agent-http-server (HTTP Server layer) @@ -191,7 +191,7 @@ ai-agent-http-server (HTTP Server layer) └── chat gpt-5-nano (AI SDK layer, LLM call 2) ---- -This shows: +The request flow demonstrates: 1. HTTP request arrives at agent 2. Agent invokes sub-agent @@ -201,9 +201,9 @@ This shows: 6. Agent makes second LLM call with tool results 7. Response returns through HTTP layer -=== Cross-service traces +=== Cross-service transcripts -When agents call MCP tools, the trace spans multiple services. Each service has a different `service.name` in the resource attributes: +When agents call MCP tools, the transcript spans multiple services. Each service has a different `service.name` in the resource attributes: * Agent spans: `"service.name": "ai-agent"` * MCP server spans: `"service.name": "mcp-{server-id}"` @@ -240,9 +240,9 @@ Redpanda Connect layer: - Component-specific attributes from your tool configuration -Use `scope.name` to filter spans by layer when analyzing traces. +Use `scope.name` to filter spans by layer when analyzing transcripts. -== Understand the trace structure +== Understand the transcript structure Each span captures a unit of work. Here's what a typical MCP tool invocation looks like: @@ -273,7 +273,7 @@ Key elements to understand: === Parent-child relationships -Traces show how operations relate. A tool invocation (parent) may trigger internal operations (children): +Transcripts show how operations relate. A tool invocation (parent) may trigger internal operations (children): [,json] ---- @@ -289,9 +289,9 @@ Traces show how operations relate. A tool invocation (parent) may trigger intern The `parentSpanId` links this child span to the parent tool invocation. Both share the same `traceId` so you can reconstruct the complete operation. -== Error events in traces +== Error events in transcripts -When something goes wrong, traces capture error details: +When something goes wrong, transcripts capture error details: [,json] ---- @@ -314,11 +314,11 @@ When something goes wrong, traces capture error details: The `events` array captures what happened and when. Use `timeUnixNano` to see exactly when the error occurred within the operation. [[opentelemetry-traces-topic]] -== How Redpanda stores traces +== How Redpanda stores trace data -The `redpanda.otel_traces` topic stores OpenTelemetry spans in JSON format, following the https://opentelemetry.io/docs/specs/otel/protocol/[OpenTelemetry Protocol (OTLP)^] specification. A Protobuf schema named `redpanda.otel_traces-value` is also automatically registered with the topic, enabling clients to deserialize trace data correctly. +The `redpanda.otel_traces` topic stores OpenTelemetry spans using Redpanda's Schema Registry wire format with a custom Protobuf schema that closely follows the https://opentelemetry.io/docs/specs/otel/protocol/[OpenTelemetry Protocol (OTLP)^] specification. A schema named `redpanda.otel_traces-value` is automatically registered in the Schema Registry with the topic, enabling clients to deserialize trace data correctly. -The `redpanda.otel_traces` topic and its schema are managed automatically by Redpanda. If you delete either the topic or the schema, they are recreated automatically. However, deleting the topic permanently deletes all trace data, and the topic comes back empty. Do not produce your own data to this topic. It is reserved for OpenTelemetry traces. +Redpanda manages both the topic and its schema automatically. If you delete either the topic or the schema, they are recreated automatically. However, deleting the topic permanently deletes all trace data, and the topic comes back empty. Do not produce your own data to this topic. It is reserved for OpenTelemetry traces. === Topic configuration and lifecycle @@ -326,20 +326,20 @@ The `redpanda.otel_traces` topic has a predefined retention policy. Configuratio The topic persists in your cluster even after all agents and MCP servers are deleted, allowing you to retain historical trace data for analysis. -Trace data may contain sensitive information from your tool inputs and outputs. Consider implementing appropriate glossterm:ACL[access control lists (ACLs)] for the `redpanda.otel_traces` topic, and review the data in traces before sharing or exporting to external systems. +Transcripts may contain sensitive information from your tool inputs and outputs. Consider implementing appropriate glossterm:ACL[access control lists (ACLs)] for the `redpanda.otel_traces` topic, and review the data in transcripts before sharing or exporting to external systems. -== Traces compared to audit logs +== Transcripts compared to audit logs -OpenTelemetry traces are designed for observability and debugging, not audit logging or compliance. +Transcripts are designed for observability and debugging, not audit logging or compliance. -Traces provide: +Transcripts provide: * Hierarchical view of request flow through your system (parent-child span relationships) * Detailed timing information for performance analysis * Ability to reconstruct execution paths and identify bottlenecks * Insights into how operations flow through distributed systems -Traces are not: +Transcripts are not: * Immutable audit records for compliance purposes * Designed for "who did what" accountability tracking diff --git a/modules/ai-agents/pages/observability/view-transcripts.adoc b/modules/ai-agents/pages/observability/view-transcripts.adoc new file mode 100644 index 000000000..dd1321f77 --- /dev/null +++ b/modules/ai-agents/pages/observability/view-transcripts.adoc @@ -0,0 +1,220 @@ += View Transcripts +:description: Learn how to filter, search, and navigate the Transcripts interface to investigate agent execution traces using multiple detail views and interactive timeline navigation. +:page-topic-type: how-to +:personas: agent_developer, platform_admin +:learning-objective-1: Filter and search transcripts to find specific execution traces +:learning-objective-2: Navigate between detail views to inspect span information at different levels +:learning-objective-3: Use the timeline interactively to navigate to specific time periods + +The Transcripts view provides filtering, searching, and navigation capabilities beyond basic timeline and hierarchy visualization. Use these features to efficiently locate and investigate specific execution traces. + +After reading this page, you will be able to: + +* [ ] {learning-objective-1} +* [ ] {learning-objective-2} +* [ ] {learning-objective-3} + +For basic Transcripts UI orientation, see xref:ai-agents:agents/monitor-agents.adoc[] or xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[]. For conceptual background on traces, see xref:ai-agents:observability/concepts.adoc[]. + +== Prerequisites + +* Running agent with at least one execution +* (permissions, ACL, auth) + +== Navigate the timeline interactively + +Use the timeline visualization to quickly identify when errors began or patterns changed, and navigate directly to transcripts from particular timestamps. + +//// + +**When to use interactive timeline:** + +* Investigating issues that occurred at known times +* Comparing behavior before and after deployments +* Analyzing execution patterns during specific events +* Correlating traces with external system changes + +//// + +== Filter and search for transcripts + +Use filters and search together to narrow down transcripts and quickly locate specific executions. + +=== Search for specific traces + +The search functionality helps you find traces by operation names, types, or identifiers: + +* Search by span names to find specific tools used +* Search by operation types (agent invocations, tool calls, LLM interactions) +* Search by trace IDs when troubleshooting and correlating with other systems + +//// + +**When to use search:** + +* Finding traces that invoked a specific tool +* Locating a trace by ID when troubleshooting +* Narrowing to specific operation types + +//// + +=== Filter by service + +Service filtering shows only traces from specific agents or MCP servers: + +* View executions from a single agent when multiple are running +* Isolate MCP server activity from agent activity +* Compare behavior across different service instances + +//// + +**When to use service filtering:** + +* Multiple agents deployed, need to focus on one +* Debugging a specific service +* Comparing activity across services + +//// + +=== Filter by execution status + +Status filtering shows traces based on their execution outcome: + +* Show successful executions for health checks +* Show only failed executions for error investigation +* Toggle between success and error views to compare and analyze patterns + +//// + +**When to use status filtering:** + +* Debugging: Focus on failures to identify error patterns +* Monitoring: Verify recent executions succeeded +* Analysis: Compare successful vs failed execution characteristics + +//// + +=== Adjust time range + +Use the time range selector to focus on specific time periods (from the last five minutes up to the last 24 hours): + +* View recent executions (for example, over the last hour) to monitor real-time activity +* Expand to longer periods for trend analysis over the last day + +//// + +* Narrow to specific time windows when investigating incidents + +**When to use time range adjustment:** + +* Investigating issues that occurred at known times +* Analyzing execution patterns over days or weeks +* Focusing on recent activity for real-time monitoring + +//// + +=== Combine multiple filters + +Filters work together for precise trace selection: + +* Service + Status: View only errors from a specific agent +* Time range + Search: Find tool invocations in a specific window +* All filters: Narrow to exact execution subset + +TIP: Apply broad filters first (time range, service) to reduce the trace set, then use search to narrow to specific operations. + + + +NOTE: Basic timeline pattern interpretation (spotting errors, gaps, trends) is covered in xref:ai-agents:agents/monitor-agents.adoc[]. + +== Inspect span details + +Selected spans display detailed information at multiple levels, from high-level summaries to complete raw data. Understanding when to use each view helps you investigate efficiently. + +=== Summary view + +The summary view provides high-level span information: + +* Operation name and type +* Execution duration and outcome +* Key metrics relevant to the operation type + +**When to use summary view:** + +* Quick status verification +* Duration comparison across operations +* Initial assessment before deeper investigation + +**Information typically shown:** + +* Total nested operations (span count) +* Execution duration +* Token usage (for LLM operations) +* Operation counts (LLM calls, tool calls) +* Service identification + +=== Detailed attributes view + +The attributes view shows structured metadata about the span: + +* Key-value pairs describing the operation +* Operation-specific parameters and results +* Context information for correlation + +**When to use attributes view:** + +* Investigating operation parameters and inputs +* Understanding error details and conditions +* Finding correlation identifiers (conversation IDs, trace IDs) +* Analyzing operation-specific metrics + +**Common attribute categories:** + +* Operation metadata (type, name, provider) +* Timing and performance data +* Input/output information (token counts, parameter values) +* Correlation identifiers +* Error information when applicable + +For details on standard attributes, see xref:ai-agents:observability/concepts.adoc#key-attributes-by-layer[]. + +=== Raw data view + +The raw data view provides the complete span structure: + +* Full OpenTelemetry span in standard format +* All fields including those not displayed in other views +* Structured data suitable for export or programmatic access + +**When to use raw data view:** + +* Debugging trace instrumentation +* Exporting span data for external analysis +* Accessing fields not shown in summarized views +* Understanding complete span structure for integration work + +**Key elements in raw structure:** + +* Trace and span identifiers for correlation +* Precise timestamps +* Complete attribute sets +* Event records (errors, custom events) +* Instrumentation metadata +* Status codes and messages + +=== Navigate between views + +Different views provide different levels of detail for the same span: + +* Start with summary for quick assessment +* Switch to attributes for detailed investigation +* Use raw data when you need complete information + +TIP: Different views show the same span data at different levels of detail - select the view that matches your current investigation needs. + +== Next steps + +* xref:ai-agents:agents/monitor-agents.adoc[] - Agent monitoring patterns (health checks, debugging) +* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] - MCP server monitoring patterns +* xref:ai-agents:observability/concepts.adoc[] - Conceptual foundation on transcripts and spans +* xref:ai-agents:agents/troubleshooting.adoc[] - Troubleshooting common issues From bd729fc2f1b3b023f4c651da24bdef7e97864ed3 Mon Sep 17 00:00:00 2001 From: Kat Batuigas Date: Mon, 26 Jan 2026 18:22:23 -0800 Subject: [PATCH 57/97] Add transcripts UI doc to nav --- modules/ROOT/nav.adoc | 4 +- .../pages/observability/concepts.adoc | 5 +- .../ai-agents/pages/observability/index.adoc | 6 + .../pages/observability/view-transcripts.adoc | 198 ++++-------------- 4 files changed, 58 insertions(+), 155 deletions(-) create mode 100644 modules/ai-agents/pages/observability/index.adoc diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index dec9deb54..bfa8577b6 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -57,7 +57,9 @@ **** xref:ai-agents:mcp/local/overview.adoc[Overview] **** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] **** xref:ai-agents:mcp/local/configuration.adoc[Configure] -** xref:ai-agents:observability/concepts.adoc[Transcripts] +** xref:ai-agents:observability/index.adoc[Transcripts] +*** xref:ai-agents:observability/concepts.adoc[Concepts] +*** xref:ai-agents:observability/view-transcripts.adoc[View Transcripts] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] diff --git a/modules/ai-agents/pages/observability/concepts.adoc b/modules/ai-agents/pages/observability/concepts.adoc index 68464f033..5866e0258 100644 --- a/modules/ai-agents/pages/observability/concepts.adoc +++ b/modules/ai-agents/pages/observability/concepts.adoc @@ -316,9 +316,9 @@ The `events` array captures what happened and when. Use `timeUnixNano` to see ex [[opentelemetry-traces-topic]] == How Redpanda stores trace data -The `redpanda.otel_traces` topic stores OpenTelemetry spans using Redpanda's Schema Registry wire format with a custom Protobuf schema that closely follows the https://opentelemetry.io/docs/specs/otel/protocol/[OpenTelemetry Protocol (OTLP)^] specification. A schema named `redpanda.otel_traces-value` is automatically registered in the Schema Registry with the topic, enabling clients to deserialize trace data correctly. +The `redpanda.otel_traces` topic stores OpenTelemetry spans using Redpanda's Schema Registry wire format with a custom Protobuf schema named `redpanda.otel_traces-value` that closely follows the https://opentelemetry.io/docs/specs/otel/protocol/[OpenTelemetry Protocol (OTLP)^] specification. This schema is automatically registered in the Schema Registry with the topic, enabling clients to deserialize trace data correctly. -Redpanda manages both the topic and its schema automatically. If you delete either the topic or the schema, they are recreated automatically. However, deleting the topic permanently deletes all trace data, and the topic comes back empty. Do not produce your own data to this topic. It is reserved for OpenTelemetry traces. +Redpanda manages both the `redpanda.otel_traces` topic and its schema automatically. If you delete either the topic or the schema, they are recreated automatically. However, deleting the topic permanently deletes all trace data, and the topic comes back empty. Do not produce your own data to this topic. It is reserved for OpenTelemetry traces. === Topic configuration and lifecycle @@ -348,5 +348,6 @@ For compliance and audit requirements, use the session and task topics for agent == Next steps +* xref:ai-agents:observability/view-transcripts.adoc[] * xref:ai-agents:agents/monitor-agents.adoc[] * xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] diff --git a/modules/ai-agents/pages/observability/index.adoc b/modules/ai-agents/pages/observability/index.adoc new file mode 100644 index 000000000..cb4b7ea73 --- /dev/null +++ b/modules/ai-agents/pages/observability/index.adoc @@ -0,0 +1,6 @@ += Transcripts +:page-layout: index +:description: Monitor agent and MCP server execution using complete OpenTelemetry traces captured by Redpanda. + +{description} + diff --git a/modules/ai-agents/pages/observability/view-transcripts.adoc b/modules/ai-agents/pages/observability/view-transcripts.adoc index dd1321f77..04affec79 100644 --- a/modules/ai-agents/pages/observability/view-transcripts.adoc +++ b/modules/ai-agents/pages/observability/view-transcripts.adoc @@ -6,7 +6,7 @@ :learning-objective-2: Navigate between detail views to inspect span information at different levels :learning-objective-3: Use the timeline interactively to navigate to specific time periods -The Transcripts view provides filtering, searching, and navigation capabilities beyond basic timeline and hierarchy visualization. Use these features to efficiently locate and investigate specific execution traces. +The Transcripts view provides filtering, searching, and navigation capabilities for investigating agent and MCP server execution transcripts. Use these features to efficiently locate specific operations, analyze performance patterns, and debug issues across tool invocations, LLM calls, and agent reasoning steps. After reading this page, you will be able to: @@ -14,207 +14,101 @@ After reading this page, you will be able to: * [ ] {learning-objective-2} * [ ] {learning-objective-3} -For basic Transcripts UI orientation, see xref:ai-agents:agents/monitor-agents.adoc[] or xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[]. For conceptual background on traces, see xref:ai-agents:observability/concepts.adoc[]. +For basic orientation on agent and MCP server monitoring, see xref:ai-agents:agents/monitor-agents.adoc[] or xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[]. For conceptual background on what transcripts capture and how spans are organized hierarchically, see xref:ai-agents:observability/concepts.adoc[]. == Prerequisites -* Running agent with at least one execution -* (permissions, ACL, auth) +* xref:ai-agents:agents/create-agent.adoc[Running agent] or xref:ai-agents:mcp/remote/quickstart.adoc[MCP server] with at least one execution +* Access to the Transcripts view (requires appropriate permissions to read the `redpanda.otel_traces` topic) -== Navigate the timeline interactively +== Navigate the Transcripts interface -Use the timeline visualization to quickly identify when errors began or patterns changed, and navigate directly to transcripts from particular timestamps. - -//// - -**When to use interactive timeline:** - -* Investigating issues that occurred at known times -* Comparing behavior before and after deployments -* Analyzing execution patterns during specific events -* Correlating traces with external system changes - -//// - -== Filter and search for transcripts +=== Use the interactive timeline -Use filters and search together to narrow down transcripts and quickly locate specific executions. - -=== Search for specific traces +Use the timeline visualization to quickly identify when errors began or patterns changed, and navigate directly to transcripts from particular timestamps. -The search functionality helps you find traces by operation names, types, or identifiers: +TIP: See xref:ai-agents:agents/monitor-agents.adoc[] and xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] to learn basic execution patterns and health indicators to investigate. -* Search by span names to find specific tools used -* Search by operation types (agent invocations, tool calls, LLM interactions) -* Search by trace IDs when troubleshooting and correlating with other systems +=== Search and filter for transcripts -//// +Use search and filters together to narrow down transcripts and quickly locate specific executions. -**When to use search:** +==== Search for specific transcripts -* Finding traces that invoked a specific tool -* Locating a trace by ID when troubleshooting -* Narrowing to specific operation types +The search functionality helps you find transcripts by operation names, span types, or identifiers: -//// +* Search by span names to find specific xref:ai-agents:observability/concepts.adoc#agent-span-types[agent operations] like `invoke_agent`, or xref:ai-agents:mcp/remote/create-tool.adoc[MCP tools] +* Search by xref:ai-agents:observability/concepts.adoc#instrumentation-layers[scope] to filter by layer (for example, `rpcn-mcp` for MCP tool spans) +* Search by trace IDs (`traceId`) when correlating with external systems or troubleshooting specific requests -=== Filter by service +==== Filter by service -Service filtering shows only traces from specific agents or MCP servers: +Service filtering shows only transcripts from specific agents or MCP servers using the `service.name` resource attribute. See xref:ai-agents:observability/concepts.adoc#cross-service-transcripts[] to understand how transcripts span multiple services. -* View executions from a single agent when multiple are running -* Isolate MCP server activity from agent activity +* View executions from a single agent when multiple are running (service name: `ai-agent`) +* Isolate MCP server activity from agent activity (service name: `mcp-{server-id}`) * Compare behavior across different service instances -//// - -**When to use service filtering:** - -* Multiple agents deployed, need to focus on one -* Debugging a specific service -* Comparing activity across services - -//// +==== Filter by execution status -=== Filter by execution status - -Status filtering shows traces based on their execution outcome: +Status filtering shows transcripts based on their execution outcome: * Show successful executions for health checks * Show only failed executions for error investigation * Toggle between success and error views to compare and analyze patterns -//// - -**When to use status filtering:** - -* Debugging: Focus on failures to identify error patterns -* Monitoring: Verify recent executions succeeded -* Analysis: Compare successful vs failed execution characteristics - -//// - -=== Adjust time range +==== Adjust time range Use the time range selector to focus on specific time periods (from the last five minutes up to the last 24 hours): * View recent executions (for example, over the last hour) to monitor real-time activity * Expand to longer periods for trend analysis over the last day +* Narrow to specific time windows when investigating issues that occurred at known times -//// - -* Narrow to specific time windows when investigating incidents - -**When to use time range adjustment:** - -* Investigating issues that occurred at known times -* Analyzing execution patterns over days or weeks -* Focusing on recent activity for real-time monitoring - -//// - -=== Combine multiple filters - -Filters work together for precise trace selection: - -* Service + Status: View only errors from a specific agent -* Time range + Search: Find tool invocations in a specific window -* All filters: Narrow to exact execution subset - -TIP: Apply broad filters first (time range, service) to reduce the trace set, then use search to narrow to specific operations. - +TIP: Apply broad filters first (time range, service) to reduce the transcript set, then use search to narrow to specific operations. +== Inspect span details -NOTE: Basic timeline pattern interpretation (spotting errors, gaps, trends) is covered in xref:ai-agents:agents/monitor-agents.adoc[]. +Each row in the transcript table represents a high-level agent or MCP server request flow. Expand each parent span to see the xref:ai-agents:observability/concepts.adoc#agent-transcript-hierarchy[hierarchical structure] of nested operations, including tool calls, LLM interactions, and internal processing steps. Parent-child spans show how operations relate: for example, an agent invocation (parent) triggers LLM calls and tool executions (children). -== Inspect span details +Selected spans display detailed information at multiple levels, from high-level summaries to complete raw data: -Selected spans display detailed information at multiple levels, from high-level summaries to complete raw data. Understanding when to use each view helps you investigate efficiently. +* Start with summary view for quick assessment +* Inspect attributes for detailed investigation +* Use raw data when you need complete information === Summary view -The summary view provides high-level span information: +The summary panel provides high-level span information: -* Operation name and type -* Execution duration and outcome -* Key metrics relevant to the operation type +* Total nested operations (span count) and execution time +* Token usage for LLM operations +* Counts of LLM calls and tool calls -**When to use summary view:** +Click on an individual span to drill down into the execution context: -* Quick status verification -* Duration comparison across operations -* Initial assessment before deeper investigation +* View the full conversation history saved for that session, including user prompts, configured xref:ai-agents:agents/create-agent.adoc#write-the-system-prompt[system prompts] to guide agent behavior, and LLM outputs +* Inspect individual tool calls made by the agent and any of its sub-agents, including request arguments and responses -**Information typically shown:** - -* Total nested operations (span count) -* Execution duration -* Token usage (for LLM operations) -* Operation counts (LLM calls, tool calls) -* Service identification +TIP: Expand the summary panel to full view to easily read long conversations. === Detailed attributes view -The attributes view shows structured metadata about the span: - -* Key-value pairs describing the operation -* Operation-specific parameters and results -* Context information for correlation - -**When to use attributes view:** - -* Investigating operation parameters and inputs -* Understanding error details and conditions -* Finding correlation identifiers (conversation IDs, trace IDs) -* Analyzing operation-specific metrics - -**Common attribute categories:** - -* Operation metadata (type, name, provider) -* Timing and performance data -* Input/output information (token counts, parameter values) -* Correlation identifiers -* Error information when applicable - -For details on standard attributes, see xref:ai-agents:observability/concepts.adoc#key-attributes-by-layer[]. +The attributes view shows structured metadata for each transcript span. Use this view to quickly locate an attribute value such as conversation ID, then paste it into the search box to find all operations from that conversation session. See xref:ai-agents:observability/concepts.adoc#key-attributes-by-layer[Transcripts and AI Observability] for details on standard attributes by instrumentation layer. === Raw data view The raw data view provides the complete span structure: -* Full OpenTelemetry span in standard format -* All fields including those not displayed in other views +* Full OpenTelemetry span in JSON format +* All fields including those not displayed in summary or attributes views * Structured data suitable for export or programmatic access -**When to use raw data view:** - -* Debugging trace instrumentation -* Exporting span data for external analysis -* Accessing fields not shown in summarized views -* Understanding complete span structure for integration work - -**Key elements in raw structure:** - -* Trace and span identifiers for correlation -* Precise timestamps -* Complete attribute sets -* Event records (errors, custom events) -* Instrumentation metadata -* Status codes and messages - -=== Navigate between views - -Different views provide different levels of detail for the same span: - -* Start with summary for quick assessment -* Switch to attributes for detailed investigation -* Use raw data when you need complete information - -TIP: Different views show the same span data at different levels of detail - select the view that matches your current investigation needs. +You can also view the raw transcript data in the `redpanda.otel_traces` topic. == Next steps -* xref:ai-agents:agents/monitor-agents.adoc[] - Agent monitoring patterns (health checks, debugging) -* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] - MCP server monitoring patterns -* xref:ai-agents:observability/concepts.adoc[] - Conceptual foundation on transcripts and spans -* xref:ai-agents:agents/troubleshooting.adoc[] - Troubleshooting common issues +* xref:ai-agents:agents/monitor-agents.adoc[] +* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] +* xref:ai-agents:observability/concepts.adoc[] +* xref:ai-agents:agents/troubleshooting.adoc[] From 37ca83ba1d930f766539575bd9b91231e86b73f0 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 27 Jan 2026 15:46:04 +0000 Subject: [PATCH 58/97] Add fraud agent tutorial --- modules/ROOT/nav.adoc | 1 + .../examples/agents/account-agent-prompt.txt | 62 ++ .../agents/compliance-agent-prompt.txt | 120 +++ .../agents/dispute-root-agent-prompt.txt | 130 ++++ .../examples/agents/fraud-agent-prompt.txt | 86 +++ .../examples/agents/merchant-agent-prompt.txt | 87 +++ .../redpanda_output_with_processors.yaml | 2 +- .../processors/calculate_fraud_score.yaml | 83 +++ .../check_regulatory_requirements.yaml | 124 ++++ .../processors/get_customer_account.yaml | 51 ++ .../processors/get_merchant_category.yaml | 90 +++ .../processors/get_risk_indicators.yaml | 123 ++++ .../processors/get_transaction_details.yaml | 99 +++ .../processors/get_transaction_history.yaml | 108 +++ .../mcp-tools/processors/log_audit_event.yaml | 43 ++ .../mcp-tools/processors/openai_chat.yaml | 2 +- .../mcp-tools/processors/verify_merchant.yaml | 119 +++ .../examples/pipelines/dispute-pipeline.yaml | 157 ++++ .../ai-agents/pages/agents/a2a-concepts.adoc | 34 +- .../pages/agents/architecture-patterns.adoc | 6 +- modules/ai-agents/pages/agents/concepts.adoc | 4 +- .../ai-agents/pages/agents/create-agent.adoc | 39 +- .../pages/agents/monitor-agents.adoc | 2 +- .../pages/agents/troubleshooting.adoc | 56 +- .../tutorials/customer-support-agent.adoc | 2 +- .../transaction-dispute-resolution.adoc | 684 ++++++++++++++++++ .../pages/observability/concepts.adoc | 4 +- 27 files changed, 2262 insertions(+), 56 deletions(-) create mode 100644 modules/ai-agents/examples/agents/account-agent-prompt.txt create mode 100644 modules/ai-agents/examples/agents/compliance-agent-prompt.txt create mode 100644 modules/ai-agents/examples/agents/dispute-root-agent-prompt.txt create mode 100644 modules/ai-agents/examples/agents/fraud-agent-prompt.txt create mode 100644 modules/ai-agents/examples/agents/merchant-agent-prompt.txt create mode 100644 modules/ai-agents/examples/mcp-tools/processors/calculate_fraud_score.yaml create mode 100644 modules/ai-agents/examples/mcp-tools/processors/check_regulatory_requirements.yaml create mode 100644 modules/ai-agents/examples/mcp-tools/processors/get_customer_account.yaml create mode 100644 modules/ai-agents/examples/mcp-tools/processors/get_merchant_category.yaml create mode 100644 modules/ai-agents/examples/mcp-tools/processors/get_risk_indicators.yaml create mode 100644 modules/ai-agents/examples/mcp-tools/processors/get_transaction_details.yaml create mode 100644 modules/ai-agents/examples/mcp-tools/processors/get_transaction_history.yaml create mode 100644 modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml create mode 100644 modules/ai-agents/examples/mcp-tools/processors/verify_merchant.yaml create mode 100644 modules/ai-agents/examples/pipelines/dispute-pipeline.yaml create mode 100644 modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index dec9deb54..6aed999d3 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -30,6 +30,7 @@ **** xref:ai-agents:agents/concepts.adoc[Concepts] **** xref:ai-agents:agents/quickstart.adoc[Quickstart] **** xref:ai-agents:agents/tutorials/customer-support-agent.adoc[Multi-Tool Orchestration] +**** xref:ai-agents:agents/tutorials/transaction-dispute-resolution.adoc[Multi-Agent Systems] *** xref:ai-agents:agents/build-index.adoc[Build Agents] **** xref:ai-agents:agents/create-agent.adoc[Create an Agent] **** xref:ai-agents:agents/prompt-best-practices.adoc[System Prompt Best Practices] diff --git a/modules/ai-agents/examples/agents/account-agent-prompt.txt b/modules/ai-agents/examples/agents/account-agent-prompt.txt new file mode 100644 index 000000000..292fa59b6 --- /dev/null +++ b/modules/ai-agents/examples/agents/account-agent-prompt.txt @@ -0,0 +1,62 @@ +You are the account agent for ACME Bank's dispute resolution system. You specialize in retrieving customer account information and transaction data. + +## Your Responsibilities + +- Look up customer account details with PII masking +- Retrieve specific transaction information +- Provide transaction pattern analysis +- Return only data available from your tools + +## Available Tools + +1. **get_customer_account**: Returns account data with masked PII + - Input: customer_id + - Returns: Name, masked email, card last 4, account type, location + +2. **get_transaction_details**: Returns detailed transaction information + - Input: transaction_id + - Returns: Amount, merchant, date, location, card used + +3. **get_transaction_history**: Returns spending pattern analysis + - Input: customer_id + - Returns: Aggregated spending patterns, categories, locations + +## PII Protection Rules + +Always return masked data: +- Email: First letter + **** + @domain (e.g., "s****@example.com") +- Phone: ***-***-XXXX (last 4 digits only) +- Card: Last 4 digits only +- Never return: Full card numbers, SSNs, full account numbers + +## Response Format + +Structure responses clearly: + +"I found the following account information: +- Customer: [Name] +- Account Type: [Type] +- Card ending in: [Last 4] +- Primary Location: [City, State, Country] + +Transaction details: +- Amount: $[Amount] +- Merchant: [Merchant Name] +- Date: [Date] +- Location: [Transaction Location]" + +## Error Handling + +If data not found: +- "I couldn't find an account for customer ID [ID]" +- "No transaction found with ID [ID]" +- Never guess or make up information + +## What You Don't Do + +- Don't calculate fraud scores (that's fraud-agent's job) +- Don't verify merchants (that's merchant-agent's job) +- Don't make recommendations about disputes +- Don't log audit events (that's compliance-agent's job) + +Your job is data retrieval only. Provide accurate, masked data and let the root agent make decisions. diff --git a/modules/ai-agents/examples/agents/compliance-agent-prompt.txt b/modules/ai-agents/examples/agents/compliance-agent-prompt.txt new file mode 100644 index 000000000..8704ecd35 --- /dev/null +++ b/modules/ai-agents/examples/agents/compliance-agent-prompt.txt @@ -0,0 +1,120 @@ +You are the compliance agent for ACME Bank's dispute resolution system. You specialize in regulatory requirements and audit logging. + +## Your Responsibilities + +- Log all dispute investigation actions for audit trail +- Check regulatory requirements for dispute types +- Verify compliance with banking regulations +- Provide timeline and documentation requirements + +## Available Tools + +1. **log_audit_event**: Log investigation actions + - Input: Transaction ID, customer ID, decision, evidence, outcome + - Returns: Audit record confirmation + +2. **check_regulatory_requirements**: Look up compliance rules + - Input: dispute_type (fraud, billing_error, service_not_received) + - Returns: Regulations, timelines, documentation requirements + +## Regulatory Frameworks + +You work with these regulations: + +1. **Regulation E (Electronic Fund Transfer Act)** + - Applies to: Fraud disputes, unauthorized transactions + - Customer liability: $50 if reported within 2 days, $500 if reported within 60 days + - Bank must provide provisional credit within 10 business days + - Investigation deadline: 90 days + +2. **Fair Credit Billing Act** + - Applies to: Billing errors, disputes + - Customer must dispute within 60 days of statement + - Bank must acknowledge within 30 days + - Resolution deadline: 90 days + +3. **Card Network Rules (Visa/Mastercard)** + - Chargeback rights and timelines + - Merchant response requirements + - Evidence requirements + +## Documentation Requirements + +For each dispute type, log: + +**Fraud Disputes:** +- Customer dispute affidavit +- Transaction details +- Fraud indicators identified +- Decision and reasoning +- Customer notification + +**Billing Errors:** +- Billing statement +- Customer dispute letter +- Merchant communication attempts +- Resolution details + +**Service Not Received:** +- Proof of non-delivery +- Merchant communication attempts +- Order/booking confirmation +- Resolution outcome + +## Timeline Tracking + +Monitor key deadlines: + +- Acknowledge dispute: 24-30 days (varies by type) +- Provisional credit: 10 business days (fraud) +- Final decision: 90 days (most disputes) +- Chargeback filing: 120 days (service issues) + +## Response Format + +For regulatory checks: + +"Compliance Requirements: + +Dispute Type: [Type] +Applicable Regulations: +- [Regulation 1] +- [Regulation 2] + +Customer Rights: +- Liability Limit: $[Amount] +- Notification Deadline: [Days] days + +Bank Obligations: +- Provisional Credit: [Required/Not Required] +- Investigation Deadline: [Days] days +- Customer Notification: [Required/Not Required] + +Documentation Required: +- [Document 1] +- [Document 2] +- [Document 3] + +Timeline: +- Acknowledge: [Timeframe] +- Decision: [Timeframe]" + +For audit logging: + +"Audit Event Logged: + +Audit ID: [UUID] +Timestamp: [ISO 8601] +Investigation Details: [Summary] +Decision: [Decision] +Evidence: [Evidence Sources] +Status: Recorded" + +## What You Don't Do + +- Don't retrieve transaction or account data +- Don't calculate fraud scores +- Don't verify merchants +- Don't make dispute recommendations + +Your job is compliance and audit only. Ensure all investigations are properly documented and regulatory requirements are met. diff --git a/modules/ai-agents/examples/agents/dispute-root-agent-prompt.txt b/modules/ai-agents/examples/agents/dispute-root-agent-prompt.txt new file mode 100644 index 000000000..a22888ddd --- /dev/null +++ b/modules/ai-agents/examples/agents/dispute-root-agent-prompt.txt @@ -0,0 +1,130 @@ +You are the root agent for a transaction dispute resolution system at ACME Bank. Your role is to orchestrate sub-agents and make final recommendations to customers about disputed transactions. + +## Your Responsibilities + +- Route customer queries to appropriate sub-agents +- Aggregate results from multiple sub-agents +- Make evidence-based recommendations +- Communicate clearly with customers +- Escalate complex cases to human agents + +## Available Sub-Agents + +You have access to four specialized sub-agents via A2A protocol: + +1. **account-agent**: Retrieves customer account data and transaction history +2. **fraud-agent**: Analyzes fraud risk and calculates risk scores +3. **merchant-agent**: Verifies merchant legitimacy and reputation +4. **compliance-agent**: Logs audit events and checks regulatory requirements + +## Decision Framework + +When investigating a dispute, follow this process: + +1. Start with account-agent to get customer and transaction details +2. Route to fraud-agent if fraud is suspected +3. Route to merchant-agent to verify merchant legitimacy +4. Route to compliance-agent to log the investigation and check requirements +5. Aggregate all evidence and make recommendation + +## Risk-Based Recommendations + +Based on aggregated evidence, take these actions: + +- **Fraud score 80-100 + high merchant risk**: Block the transaction immediately, block the card, issue new card +- **Fraud score 60-79**: Hold for specialist review, temporary card block +- **Fraud score 40-59**: Ask customer to verify with merchant first before taking action +- **Fraud score 0-39**: Likely legitimate transaction, help customer recall the purchase + +## Escalation Criteria + +Escalate to human agent when: + +- Fraud score is medium (40-70) and evidence is conflicting +- Customer disputes the recommendation strongly +- Regulatory requirements exceed available tools +- Subscription or recurring billing issues require merchant intervention + +## Compliance Constraints + +Never: + +- Expose full credit card numbers or SSNs (use masked versions only) +- Make guarantees about dispute outcomes (use "likely" or "recommend") +- Process disputes without logging to compliance-agent +- Reveal internal fraud detection logic or merchant scoring details to customers +- Make decisions without sub-agent evidence +- Ask customers for screenshots or additional proof (you have the transaction records) + +## Customer Communication Style + +**Clear, bank-appropriate language:** +- Use "I've reviewed your account" not "I called the account-agent" +- Use "this charge doesn't match your typical spending" not "fraud score is 95/100" +- Use "I'm blocking this card" not "I recommend you freeze it" +- Don't reveal merchant reputation scores or fraud report counts + +**Proactive protection:** +For likely fraud (score 80+): +- Block the card immediately: "I'm blocking your card ending in [XXXX] right now to prevent additional fraudulent charges" +- Issue replacement: "We'll send you a replacement card with a new number" +- Process the claim: "You'll see the credit for this charge within 10 business days" + +For uncertain cases (score 40-79): +- Temporary block: "I'm placing a temporary hold on this card while we investigate" +- Escalate: "A specialist will contact you within 24 hours" + +**Concise responses:** +Keep responses to 3-4 short paragraphs maximum. Customers want action, not detailed analysis. + +## Example Investigation Flow + +Customer: "I see a $1,847.99 charge from 'LUXURY WATCHES INT' in Singapore on transaction TXN-89012. This is fraud. My customer ID is CUST-1001." + +**Your response to customer:** + +"I've reviewed your account and this transaction. This charge doesn't match your typical spending pattern, and you haven't made international purchases in the past 90 days. + +Here's what I'm doing: +- Blocking your card ending in 4532 right now to prevent any additional unauthorized charges +- Approving your dispute for the full $1,847.99 - you'll see the credit within 10 business days +- Sending you a replacement card with a new number within 5-7 business days + +Your dispute has been logged and meets the requirements under Regulation E for unauthorized electronic fund transfers. + +Is there anything else I can help you with today?" + +**What you actually did behind the scenes:** +1. Called account-agent → confirmed US-based customer, no international history +2. Called fraud-agent → received score 95/100 (critical risk) +3. Called merchant-agent → confirmed high fraud indicators +4. Called compliance-agent → logged under Regulation E +5. Made decision: transaction is fraudulent, block card immediately + +(Don't share the scores or technical details with the customer) + +**Note:** When talking to customers, use natural banking language like "approving your dispute." But for programmatic JSON responses, "recommendation" describes the TRANSACTION status, not the dispute claim status. + +## Programmatic Invocations + +When invoked from a pipeline or automated system (you'll receive transaction data without conversational context), respond with ONLY valid JSON. No explanatory text, no markdown formatting, no commentary before or after - just the JSON object. + +Required JSON format: +{ + "recommendation": "block_and_investigate" | "hold_for_review" | "approve", + "fraud_score": , + "confidence": "high" | "medium" | "low", + "reasoning": "" +} + +**Recommendation field definitions:** +- **"block_and_investigate"**: Transaction is fraudulent. Block the card immediately and investigate. +- **"hold_for_review"**: Unclear if fraudulent. Place temporary hold and escalate to human specialist. +- **"approve"**: Transaction is legitimate. Customer likely forgot about it or needs clarification. + +**Mapping from conversational actions:** +- If you would block the card → use "block_and_investigate" +- If you would escalate to specialist → use "hold_for_review" +- If transaction seems legitimate → use "approve" + +The pipeline will parse this JSON to make automated decisions. Any non-JSON response will cause processing failures. diff --git a/modules/ai-agents/examples/agents/fraud-agent-prompt.txt b/modules/ai-agents/examples/agents/fraud-agent-prompt.txt new file mode 100644 index 000000000..69db69a99 --- /dev/null +++ b/modules/ai-agents/examples/agents/fraud-agent-prompt.txt @@ -0,0 +1,86 @@ +You are the fraud detection agent for ACME Bank's dispute resolution system. You specialize in analyzing transactions for fraud indicators and calculating risk scores. + +## Your Responsibilities + +- Calculate fraud risk scores (0-100 scale) +- Identify specific fraud indicators +- Provide risk assessment reasoning +- Return confidence levels with assessments + +## Available Tools + +1. **calculate_fraud_score**: Multi-factor fraud scoring + - Input: Transaction data, customer data, merchant reputation + - Returns: Fraud score, breakdown by factor, recommendation + +2. **get_risk_indicators**: Detailed fraud signal detection + - Input: transaction_id + - Returns: Array of risk indicators with severity levels + +## Risk Scoring Factors + +Consider these factors: + +1. **Location Risk** (0-35 points) + - International vs. customer's country + - City mismatch from customer's primary location + - High-risk countries + +2. **Merchant Risk** (0-30 points) + - Merchant reputation score + - Fraud report history + - Business verification status + +3. **Amount Risk** (0-25 points) + - Deviation from customer's average + - Unusually large for merchant category + - Round numbers (potential testing) + +4. **Velocity Risk** (0-15 points) + - Multiple transactions in short timeframe + - Rapid succession of purchases + - Geographic impossibility + +5. **Category Risk** (0-20 points) + - Outside customer's typical categories + - High-risk MCC codes + - Mismatch with spending patterns + +## Risk Levels + +- **Critical (80-100)**: Almost certainly fraud, immediate action needed +- **High (60-79)**: Strong fraud indicators, hold for review +- **Medium (40-59)**: Some concerning factors, customer verification recommended +- **Low (20-39)**: Minor flags, likely legitimate +- **Minimal (0-19)**: No significant fraud indicators + +## Response Format + +Structure your analysis: + +"Fraud Risk Analysis: + +Fraud Score: [Score]/100 - [Risk Level] + +Risk Breakdown: +- Location Risk: [Score] - [Explanation] +- Merchant Risk: [Score] - [Explanation] +- Amount Risk: [Score] - [Explanation] +- Velocity Risk: [Score] - [Explanation] +- Category Risk: [Score] - [Explanation] + +Key Indicators: +- [Indicator 1] +- [Indicator 2] +- [Indicator 3] + +Recommendation: [block_and_investigate | hold_for_review | monitor_closely | approve]" + +## What You Don't Do + +- Don't retrieve account or transaction data (use what's provided) +- Don't verify merchants (that's merchant-agent's job) +- Don't make final dispute decisions (provide recommendation only) +- Don't log audit events + +Your job is fraud analysis only. Provide objective risk assessment based on available data. diff --git a/modules/ai-agents/examples/agents/merchant-agent-prompt.txt b/modules/ai-agents/examples/agents/merchant-agent-prompt.txt new file mode 100644 index 000000000..bb6ee31da --- /dev/null +++ b/modules/ai-agents/examples/agents/merchant-agent-prompt.txt @@ -0,0 +1,87 @@ +You are the merchant verification agent for ACME Bank's dispute resolution system. You specialize in verifying merchant legitimacy and reputation. + +## Your Responsibilities + +- Verify merchant reputation and legitimacy +- Look up merchant category codes (MCC) +- Identify known fraud patterns for merchant categories +- Provide merchant-specific insights + +## Available Tools + +1. **verify_merchant**: Merchant reputation lookup + - Input: merchant_name + - Returns: Reputation score, fraud reports, business verification, red flags + +2. **get_merchant_category**: MCC code analysis + - Input: mcc (4-digit code) + - Returns: Category details, typical transaction ranges, fraud risk profile + +## Reputation Scoring + +Interpret reputation scores: + +- **90-100**: Excellent, trusted merchant +- **70-89**: Good, established business +- **50-69**: Moderate, some concerns +- **30-49**: Poor, significant red flags +- **0-29**: High risk, strong fraud indicators + +## Red Flags to Report + +Watch for: +- High volume of fraud reports +- Recently established businesses in high-risk categories +- Unverified business registration +- Pattern of chargebacks +- Operates in high-risk jurisdictions +- Billing descriptor mismatches + +## Common Merchant Issues + +Be aware of legitimate merchant problems: + +- **Subscription services**: Known for duplicate billing, difficult cancellation +- **International hotels**: Currency conversion confusion, incidental charges +- **Online marketplaces**: Third-party sellers, billing descriptor confusion +- **Travel booking**: Pre-authorization holds, cancellation fee disputes + +## Response Format + +Structure your verification: + +"Merchant Verification Results: + +Merchant: [Name] +Reputation Score: [Score]/100 - [Level] +Verification Status: [Verified | Unverified | Unknown] + +Business Details: +- Country: [Country] +- Years in Operation: [Years] +- Registration: [Verified/Unverified] + +Fraud Reports: +- Total Reports: [Count] +- Recent (30 days): [Count] +- Confirmed Fraud Cases: [Count] + +Category Analysis (MCC [Code]): +- Category: [Category Name] +- Risk Profile: [High/Medium/Low] +- Typical Transaction Range: $[Min]-$[Max] + +Red Flags: +- [Flag 1] +- [Flag 2] + +Recommendation: [trusted_merchant | verify_subscription_details | manual_review_required | block_merchant]" + +## What You Don't Do + +- Don't calculate fraud scores (that's fraud-agent's job) +- Don't retrieve transaction data (that's account-agent's job) +- Don't make final dispute decisions +- Don't log audit events + +Your job is merchant verification only. Provide objective assessment of merchant legitimacy. diff --git a/modules/ai-agents/examples/mcp-tools/outputs/redpanda_output_with_processors.yaml b/modules/ai-agents/examples/mcp-tools/outputs/redpanda_output_with_processors.yaml index eea4b323f..30e4a387b 100644 --- a/modules/ai-agents/examples/mcp-tools/outputs/redpanda_output_with_processors.yaml +++ b/modules/ai-agents/examples/mcp-tools/outputs/redpanda_output_with_processors.yaml @@ -3,7 +3,7 @@ label: summarize_and_publish processors: - openai_chat_completion: api_key: "${secrets.OPENAI_API_KEY}" - model: "gpt-4" + model: "gpt-5.2" prompt: ${! json("question") } - mapping: | root.question = this.question diff --git a/modules/ai-agents/examples/mcp-tools/processors/calculate_fraud_score.yaml b/modules/ai-agents/examples/mcp-tools/processors/calculate_fraud_score.yaml new file mode 100644 index 000000000..b0fecb828 --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/calculate_fraud_score.yaml @@ -0,0 +1,83 @@ +label: calculate_fraud_score +mapping: | + let location_risk = match { + this.transaction.location.country != this.customer.location_country => 35, + this.transaction.location.city != this.customer.primary_city => 15, + _ => 0 + } + + let merchant_risk = match { + this.merchant_reputation < 40 => 30, + this.merchant_reputation < 70 => 15, + _ => 0 + } + + let amount_risk = match { + this.transaction.amount > (this.customer.avg_transaction * 10) => 25, + this.transaction.amount > (this.customer.avg_transaction * 5) => 15, + this.transaction.amount > (this.customer.avg_transaction * 2) => 5, + _ => 0 + } + + let velocity_risk = match { + this.recent_transactions_count > 10 => 15, + this.recent_transactions_count > 5 => 8, + _ => 0 + } + + let category_risk = match { + this.transaction.merchant.category == "jewelry" && this.customer.typical_categories.contains("jewelry").not() => 20, + this.transaction.merchant.category == "electronics" && this.customer.typical_categories.contains("electronics").not() => 15, + this.transaction.merchant.category == "luxury_goods" && this.customer.typical_categories.contains("luxury_goods").not() => 15, + _ => 0 + } + + let total_score = $location_risk + $merchant_risk + $amount_risk + $velocity_risk + $category_risk + + let risk_level = match { + $total_score >= 80 => "critical", + $total_score >= 60 => "high", + $total_score >= 40 => "medium", + $total_score >= 20 => "low", + _ => "minimal" + } + + root = { + "transaction_id": this.transaction.transaction_id, + "fraud_score": $total_score, + "risk_level": $risk_level, + "score_breakdown": { + "location_risk": $location_risk, + "merchant_risk": $merchant_risk, + "amount_risk": $amount_risk, + "velocity_risk": $velocity_risk, + "category_risk": $category_risk + }, + "factors_detected": [ + if $location_risk > 0 { "unusual_location" }, + if $merchant_risk > 0 { "questionable_merchant" }, + if $amount_risk > 0 { "unusual_amount" }, + if $velocity_risk > 0 { "high_velocity" }, + if $category_risk > 0 { "unusual_category" } + ].filter(f -> f != null), + "recommendation": match { + $total_score >= 80 => "block_and_investigate", + $total_score >= 60 => "hold_for_review", + $total_score >= 40 => "monitor_closely", + _ => "approve" + } + } + +meta: + mcp: + enabled: true + description: "Calculate fraud risk score based on transaction patterns and risk indicators. Returns risk level and recommendation." + properties: + - name: transaction_id + type: string + description: "Transaction identifier to analyze (format TXN-XXXXX)" + required: true + - name: customer_id + type: string + description: "Customer identifier for historical analysis (format CUST-XXXX)" + required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/check_regulatory_requirements.yaml b/modules/ai-agents/examples/mcp-tools/processors/check_regulatory_requirements.yaml new file mode 100644 index 000000000..a3a6e189e --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/check_regulatory_requirements.yaml @@ -0,0 +1,124 @@ +label: check_regulatory_requirements +mapping: | + root = match { + this.dispute_type == "fraud" => { + "dispute_type": "fraud", + "regulations_applicable": [ + "Regulation E (Electronic Fund Transfer Act)", + "Fair Credit Billing Act", + "Card Network Rules (Visa/Mastercard)" + ], + "customer_rights": { + "liability_limit": 50.00, + "zero_liability_if_reported_promptly": true, + "notification_deadline_days": 60 + }, + "bank_obligations": { + "provisional_credit_required": true, + "provisional_credit_deadline_days": 10, + "investigation_deadline_days": 90, + "customer_notification_required": true + }, + "documentation_required": [ + "Customer dispute affidavit", + "Transaction details", + "Customer communication log", + "Investigation findings" + ], + "timeline": { + "acknowledge_dispute_hours": 24, + "provisional_credit_days": 10, + "final_decision_days": 90 + } + }, + this.dispute_type == "billing_error" => { + "dispute_type": "billing_error", + "regulations_applicable": [ + "Fair Credit Billing Act", + "Regulation Z (Truth in Lending)" + ], + "customer_rights": { + "dispute_window_days": 60, + "interest_suspension": true + }, + "bank_obligations": { + "acknowledge_dispute_days": 30, + "investigation_deadline_days": 90, + "correction_required_if_error_found": true + }, + "documentation_required": [ + "Billing statement", + "Customer dispute letter", + "Merchant communication (if any)", + "Investigation results" + ], + "timeline": { + "acknowledge_dispute_days": 30, + "resolution_days": 90 + } + }, + this.dispute_type == "service_not_received" => { + "dispute_type": "service_not_received", + "regulations_applicable": [ + "Fair Credit Billing Act", + "Card Network Chargeback Rules" + ], + "customer_rights": { + "chargeback_eligibility": true, + "dispute_window_days": 120 + }, + "bank_obligations": { + "verify_merchant_response": true, + "chargeback_processing_days": 45 + }, + "documentation_required": [ + "Proof of non-delivery or service failure", + "Merchant communication attempts", + "Order/booking confirmation", + "Merchant response (if obtained)" + ], + "timeline": { + "merchant_response_wait_days": 15, + "chargeback_filing_days": 120 + } + }, + _ => { + "dispute_type": "general", + "regulations_applicable": [ + "Fair Credit Billing Act" + ], + "customer_rights": { + "dispute_right": true, + "dispute_window_days": 60 + }, + "bank_obligations": { + "investigation_required": true, + "customer_notification_required": true + }, + "documentation_required": [ + "Customer dispute statement", + "Transaction evidence" + ], + "timeline": { + "standard_review_days": 30 + } + } + } + +meta: + mcp: + enabled: true + description: "Check regulatory requirements for dispute resolution based on transaction type, amount, and jurisdiction." + properties: + - name: transaction_type + type: string + description: "Type of transaction (card_not_present, international, recurring, travel)" + required: true + - name: amount + type: number + description: "Transaction amount in USD" + required: true + - name: jurisdiction + type: string + description: "Geographic jurisdiction (USA, EU, APAC)" + required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_customer_account.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_customer_account.yaml new file mode 100644 index 000000000..9701bb209 --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/get_customer_account.yaml @@ -0,0 +1,51 @@ +label: get_customer_account +mapping: | + root = match { + this.customer_id == "CUST-1001" => { + "customer_id": "CUST-1001", + "name": "Dana A.", + "email": "s****@example.com", + "account_type": "premium_checking", + "card_last_four": "4532", + "card_status": "active", + "member_since": "2019-03-15", + "location": "Seattle, WA, USA", + "phone_masked": "***-***-7890" + }, + this.customer_id == "CUST-1002" => { + "customer_id": "CUST-1002", + "name": "Alex T.", + "email": "m****@example.com", + "account_type": "standard_checking", + "card_last_four": "8821", + "card_status": "active", + "member_since": "2021-07-22", + "location": "San Francisco, CA, USA", + "phone_masked": "***-***-4521" + }, + this.customer_id == "CUST-1003" => { + "customer_id": "CUST-1003", + "name": "Quinn N.", + "email": "e****@example.com", + "account_type": "premium_credit", + "card_last_four": "2193", + "card_status": "active", + "member_since": "2020-11-08", + "location": "Austin, TX, USA", + "phone_masked": "***-***-3344" + }, + _ => { + "error": "customer_not_found", + "message": "No account found for customer ID: " + this.customer_id + } + } + +meta: + mcp: + enabled: true + description: "Retrieve customer account information with masked PII. Use CUST-1001, CUST-1002, or CUST-1003 for testing." + properties: + - name: customer_id + type: string + description: "Customer identifier (format CUST-XXXX)" + required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_merchant_category.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_merchant_category.yaml new file mode 100644 index 000000000..b8dc484da --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/get_merchant_category.yaml @@ -0,0 +1,90 @@ +label: get_merchant_category +mapping: | + root = match { + this.mcc == "5944" => { + "mcc": "5944", + "category": "Jewelry, Watch, Clock, and Silverware Stores", + "high_level_category": "retail_luxury", + "risk_profile": "high", + "typical_transaction_range": { + "min": 100, + "max": 5000, + "average": 850 + }, + "fraud_risk_notes": "High-value items, common fraud target, verify customer intent", + "common_fraud_patterns": [ + "Stolen card purchases", + "Account takeover", + "Reshipping schemes" + ] + }, + this.mcc == "5942" => { + "mcc": "5942", + "category": "Book Stores", + "high_level_category": "retail_general", + "risk_profile": "low", + "typical_transaction_range": { + "min": 10, + "max": 200, + "average": 45 + }, + "fraud_risk_notes": "Low fraud risk, common online purchase category", + "common_fraud_patterns": [] + }, + this.mcc == "4899" => { + "mcc": "4899", + "category": "Cable, Satellite, and Other Pay Television and Radio Services", + "high_level_category": "subscription_services", + "risk_profile": "medium", + "typical_transaction_range": { + "min": 9.99, + "max": 99.99, + "average": 29.99 + }, + "fraud_risk_notes": "Recurring billing, watch for duplicate charges and unauthorized subscriptions", + "common_fraud_patterns": [ + "Duplicate subscriptions", + "Unauthorized recurring charges", + "Failed cancellation processing" + ] + }, + this.mcc == "7011" => { + "mcc": "7011", + "category": "Lodging - Hotels, Motels, Resorts", + "high_level_category": "travel_hospitality", + "risk_profile": "medium", + "typical_transaction_range": { + "min": 80, + "max": 500, + "average": 180 + }, + "fraud_risk_notes": "Verify travel patterns, check for location consistency", + "common_fraud_patterns": [ + "Stolen card at booking sites", + "Account takeover for rewards redemption" + ] + }, + _ => { + "mcc": this.mcc, + "category": "Unknown Category", + "high_level_category": "unclassified", + "risk_profile": "unknown", + "typical_transaction_range": { + "min": 0, + "max": 0, + "average": 0 + }, + "fraud_risk_notes": "MCC not recognized, manual review recommended", + "common_fraud_patterns": [] + } + } + +meta: + mcp: + enabled: true + description: "Retrieve merchant category information including MCC code, fraud risk level, and common patterns. Use LUXURY WATCHES INT, EXAMPLE STREAMING, or HOTEL PARIS for testing." + properties: + - name: merchant_name + type: string + description: "Merchant name as it appears on transaction" + required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_risk_indicators.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_risk_indicators.yaml new file mode 100644 index 000000000..410990dd2 --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/get_risk_indicators.yaml @@ -0,0 +1,123 @@ +label: get_risk_indicators +mapping: | + root = match { + this.transaction_id == "TXN-89012" => { + "transaction_id": "TXN-89012", + "risk_indicators": [ + { + "indicator": "international_transaction", + "severity": "high", + "description": "Transaction originated from Singapore, customer has no international transaction history" + }, + { + "indicator": "first_time_merchant", + "severity": "medium", + "description": "Customer has never transacted with this merchant before" + }, + { + "indicator": "unusual_category", + "severity": "high", + "description": "Jewelry purchase is outside customer's typical spending categories" + }, + { + "indicator": "high_amount", + "severity": "high", + "description": "Transaction amount is 14.5x customer's average transaction" + }, + { + "indicator": "merchant_flagged", + "severity": "critical", + "description": "Merchant has been flagged in fraud databases" + } + ], + "total_indicators": 5, + "critical_count": 1, + "high_count": 3, + "medium_count": 1, + "overall_assessment": "high_fraud_probability" + }, + this.transaction_id == "TXN-89013" => { + "transaction_id": "TXN-89013", + "risk_indicators": [ + { + "indicator": "known_merchant", + "severity": "none", + "description": "Example Marketplace is a recognized and trusted merchant" + } + ], + "total_indicators": 1, + "critical_count": 0, + "high_count": 0, + "medium_count": 0, + "overall_assessment": "low_fraud_probability" + }, + this.transaction_id == "TXN-89014" => { + "transaction_id": "TXN-89014", + "risk_indicators": [ + { + "indicator": "recurring_billing", + "severity": "low", + "description": "Subscription service with recurring charges" + }, + { + "indicator": "merchant_billing_issues", + "severity": "medium", + "description": "Merchant has known history of duplicate billing complaints" + }, + { + "indicator": "duplicate_charge_pattern", + "severity": "medium", + "description": "Multiple charges detected from same merchant in short timeframe" + } + ], + "total_indicators": 3, + "critical_count": 0, + "high_count": 0, + "medium_count": 2, + "overall_assessment": "medium_fraud_probability" + }, + this.transaction_id == "TXN-89015" => { + "transaction_id": "TXN-89015", + "risk_indicators": [ + { + "indicator": "international_transaction", + "severity": "low", + "description": "Transaction in France matches customer's travel history" + }, + { + "indicator": "travel_category", + "severity": "none", + "description": "Hotel charge is consistent with customer's frequent travel patterns" + }, + { + "indicator": "timing_matches_travel", + "severity": "none", + "description": "Transaction date aligns with customer's Paris trip" + } + ], + "total_indicators": 3, + "critical_count": 0, + "high_count": 0, + "medium_count": 0, + "overall_assessment": "low_fraud_probability" + }, + _ => { + "transaction_id": this.transaction_id, + "risk_indicators": [], + "total_indicators": 0, + "critical_count": 0, + "high_count": 0, + "medium_count": 0, + "overall_assessment": "insufficient_data" + } + } + +meta: + mcp: + enabled: true + description: "Retrieve fraud risk indicators for a transaction including severity levels and overall assessment. Use TXN-89012 through TXN-89015 for testing." + properties: + - name: transaction_id + type: string + description: "Transaction identifier to analyze (format TXN-XXXXX)" + required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_transaction_details.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_transaction_details.yaml new file mode 100644 index 000000000..1943fbcd6 --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/get_transaction_details.yaml @@ -0,0 +1,99 @@ +label: get_transaction_details +mapping: | + root = match { + this.transaction_id == "TXN-89012" => { + "transaction_id": "TXN-89012", + "customer_id": "CUST-1001", + "amount": 1847.99, + "currency": "USD", + "merchant": { + "name": "LUXURY WATCHES INT", + "category": "jewelry", + "country": "Singapore", + "mcc": "5944" + }, + "card_last_four": "4532", + "date": "2026-01-18T14:22:00Z", + "location": { + "city": "Singapore", + "country": "SG", + "coordinates": "1.3521,103.8198" + }, + "status": "posted" + }, + this.transaction_id == "TXN-89013" => { + "transaction_id": "TXN-89013", + "customer_id": "CUST-1001", + "amount": 47.83, + "currency": "USD", + "merchant": { + "name": "EXAMPLE MKTPLACE", + "category": "online_retail", + "country": "USA", + "mcc": "5942" + }, + "card_last_four": "4532", + "date": "2026-01-15T10:15:00Z", + "location": { + "city": "Seattle", + "country": "US", + "coordinates": "47.6062,-122.3321" + }, + "status": "posted" + }, + this.transaction_id == "TXN-89014" => { + "transaction_id": "TXN-89014", + "customer_id": "CUST-1002", + "amount": 29.99, + "currency": "USD", + "merchant": { + "name": "EXAMPLE STREAMING", + "category": "subscription_service", + "country": "USA", + "mcc": "4899" + }, + "card_last_four": "8821", + "date": "2025-12-15T00:00:01Z", + "location": { + "city": "San Francisco", + "country": "US", + "coordinates": "37.7749,-122.4194" + }, + "status": "posted", + "recurring": true + }, + this.transaction_id == "TXN-89015" => { + "transaction_id": "TXN-89015", + "customer_id": "CUST-1003", + "amount": 312.50, + "currency": "EUR", + "merchant": { + "name": "HOTEL PARIS", + "category": "lodging", + "country": "France", + "mcc": "7011" + }, + "card_last_four": "2193", + "date": "2026-01-10T20:30:00Z", + "location": { + "city": "Paris", + "country": "FR", + "coordinates": "48.8566,2.3522" + }, + "status": "posted" + }, + _ => { + "error": "transaction_not_found", + "message": "No transaction found with ID: " + this.transaction_id + } + } + +meta: + mcp: + enabled: true + description: "Retrieve detailed transaction information including merchant, location, and amount. Use TXN-89012 through TXN-89015 for testing." + properties: + - name: transaction_id + type: string + description: "Transaction identifier (format TXN-XXXXX)" + required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_transaction_history.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_transaction_history.yaml new file mode 100644 index 000000000..3c8107fd3 --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/get_transaction_history.yaml @@ -0,0 +1,108 @@ +label: get_transaction_history +mapping: | + root = match { + this.customer_id == "CUST-1001" => { + "customer_id": "CUST-1001", + "analysis_period": "last_90_days", + "spending_patterns": { + "average_transaction": 127.45, + "median_transaction": 65.20, + "total_transactions": 87, + "total_amount": 11088.15 + }, + "category_breakdown": [ + {"category": "online_retail", "count": 42, "avg_amount": 78.50}, + {"category": "groceries", "count": 28, "avg_amount": 95.30}, + {"category": "restaurants", "count": 12, "avg_amount": 45.80}, + {"category": "gas_stations", "count": 5, "avg_amount": 62.00} + ], + "location_patterns": { + "primary_region": "US_West_Coast", + "international_transactions": 0, + "cities": ["Seattle", "Bellevue", "Tacoma"] + }, + "merchant_patterns": { + "recurring_merchants": ["EXAMPLE MKTPLACE", "EXAMPLE WHOLESALE", "EXAMPLE COFFEE"], + "first_time_merchants_this_period": 3 + }, + "risk_indicators": { + "unusual_activity": false, + "velocity_flags": 0, + "declined_transactions": 1 + } + }, + this.customer_id == "CUST-1002" => { + "customer_id": "CUST-1002", + "analysis_period": "last_90_days", + "spending_patterns": { + "average_transaction": 95.33, + "median_transaction": 52.10, + "total_transactions": 64, + "total_amount": 6101.12 + }, + "category_breakdown": [ + {"category": "subscription_service", "count": 15, "avg_amount": 29.99}, + {"category": "restaurants", "count": 25, "avg_amount": 68.40}, + {"category": "online_retail", "count": 18, "avg_amount": 110.20}, + {"category": "entertainment", "count": 6, "avg_amount": 45.00} + ], + "location_patterns": { + "primary_region": "US_West_Coast", + "international_transactions": 0, + "cities": ["San Francisco", "Oakland", "San Jose"] + }, + "merchant_patterns": { + "recurring_merchants": ["EXAMPLE STREAMING", "EXAMPLE MEDIA", "EXAMPLE AUDIO"], + "first_time_merchants_this_period": 7 + }, + "risk_indicators": { + "unusual_activity": false, + "velocity_flags": 0, + "declined_transactions": 0 + } + }, + this.customer_id == "CUST-1003" => { + "customer_id": "CUST-1003", + "analysis_period": "last_90_days", + "spending_patterns": { + "average_transaction": 215.67, + "median_transaction": 145.00, + "total_transactions": 52, + "total_amount": 11214.84 + }, + "category_breakdown": [ + {"category": "travel", "count": 8, "avg_amount": 650.00}, + {"category": "lodging", "count": 6, "avg_amount": 380.50}, + {"category": "restaurants", "count": 22, "avg_amount": 85.20}, + {"category": "online_retail", "count": 16, "avg_amount": 95.75} + ], + "location_patterns": { + "primary_region": "US_South", + "international_transactions": 3, + "cities": ["Austin", "Houston", "Dallas", "Paris", "London"] + }, + "merchant_patterns": { + "recurring_merchants": ["EXAMPLE AIRLINES", "EXAMPLE HOTEL", "EXAMPLE TRAVEL"], + "first_time_merchants_this_period": 12 + }, + "risk_indicators": { + "unusual_activity": false, + "velocity_flags": 0, + "declined_transactions": 0 + } + }, + _ => { + "error": "customer_not_found", + "message": "No transaction history found for customer ID: " + this.customer_id + } + } + +meta: + mcp: + enabled: true + description: "Retrieve customer transaction history with spending patterns, category breakdown, and risk indicators. Use CUST-1001, CUST-1002, or CUST-1003 for testing." + properties: + - name: customer_id + type: string + description: "Customer identifier (format CUST-XXXX)" + required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml b/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml new file mode 100644 index 000000000..ade8773ff --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml @@ -0,0 +1,43 @@ +label: log_audit_event +mapping: | + root = { + "audit_id": uuid_v4(), + "timestamp": now(), + "event_type": "dispute_investigation", + "transaction_id": this.transaction_id, + "customer_id": this.customer_id, + "agent_decision": this.decision, + "risk_score": this.risk_score, + "evidence_reviewed": this.evidence, + "outcome": this.outcome, + "escalated": this.escalated, + "compliance_notes": this.notes, + "logged_by": "dispute-resolution-agent", + "status": "recorded" + } + +meta: + mcp: + enabled: true + description: "Log compliance audit events for dispute resolution. Records customer ID, transaction details, decision, and notes." + properties: + - name: customer_id + type: string + description: "Customer identifier (format CUST-XXXX)" + required: true + - name: transaction_id + type: string + description: "Transaction identifier (format TXN-XXXXX)" + required: true + - name: decision + type: string + description: "Dispute resolution decision (approve_refund, deny_claim, etc.)" + required: true + - name: escalated + type: boolean + description: "Whether case was escalated for manual review" + required: false + - name: notes + type: string + description: "Additional compliance notes" + required: false diff --git a/modules/ai-agents/examples/mcp-tools/processors/openai_chat.yaml b/modules/ai-agents/examples/mcp-tools/processors/openai_chat.yaml index 55da6473d..fa5e81c63 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/openai_chat.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/openai_chat.yaml @@ -5,7 +5,7 @@ label: analyze-feedback # tag::component[] openai_chat_completion: api_key: "${secrets.OPENAI_API_KEY}" - model: "gpt-4" + model: "gpt-5.2" prompt: | Analyze this customer feedback and provide: 1. Sentiment (positive/negative/neutral) diff --git a/modules/ai-agents/examples/mcp-tools/processors/verify_merchant.yaml b/modules/ai-agents/examples/mcp-tools/processors/verify_merchant.yaml new file mode 100644 index 000000000..d35ddda8c --- /dev/null +++ b/modules/ai-agents/examples/mcp-tools/processors/verify_merchant.yaml @@ -0,0 +1,119 @@ +label: verify_merchant +mapping: | + root = match { + this.merchant_name == "LUXURY WATCHES INT" => { + "merchant_name": "LUXURY WATCHES INT", + "merchant_id": "MER-99912", + "reputation_score": 12, + "reputation_level": "high_risk", + "verification_status": "unverified", + "fraud_reports": { + "total_reports": 247, + "recent_reports_30d": 42, + "confirmed_fraud_cases": 89 + }, + "business_details": { + "country": "Singapore", + "years_in_operation": 1, + "registration_verified": false + }, + "red_flags": [ + "High volume of fraud reports", + "Recently established business", + "Unverified business registration", + "Operates in high-risk category", + "Pattern of chargebacks" + ], + "recommendation": "block_merchant" + }, + this.merchant_name == "EXAMPLE MKTPLACE" => { + "merchant_name": "EXAMPLE MKTPLACE", + "merchant_id": "MER-00001", + "reputation_score": 98, + "reputation_level": "excellent", + "verification_status": "verified", + "fraud_reports": { + "total_reports": 1203, + "recent_reports_30d": 15, + "confirmed_fraud_cases": 0 + }, + "business_details": { + "country": "USA", + "years_in_operation": 20, + "registration_verified": true, + "parent_company": "Example Organization" + }, + "red_flags": [], + "recommendation": "trusted_merchant" + }, + this.merchant_name == "EXAMPLE STREAMING" => { + "merchant_name": "EXAMPLE STREAMING", + "merchant_id": "MER-45678", + "reputation_score": 65, + "reputation_level": "moderate", + "verification_status": "verified", + "fraud_reports": { + "total_reports": 892, + "recent_reports_30d": 67, + "confirmed_fraud_cases": 12 + }, + "business_details": { + "country": "USA", + "years_in_operation": 5, + "registration_verified": true + }, + "red_flags": [ + "Known billing system issues", + "Frequent duplicate charge complaints", + "Difficult cancellation process" + ], + "common_issues": [ + "Duplicate subscriptions", + "Failed cancellation processing", + "Unclear billing descriptors" + ], + "recommendation": "verify_subscription_details" + }, + this.merchant_name == "HOTEL PARIS" => { + "merchant_name": "HOTEL PARIS", + "merchant_id": "MER-78234", + "reputation_score": 88, + "reputation_level": "trusted", + "verification_status": "verified", + "fraud_reports": { + "total_reports": 45, + "recent_reports_30d": 2, + "confirmed_fraud_cases": 0 + }, + "business_details": { + "country": "France", + "years_in_operation": 15, + "registration_verified": true, + "chain": "Independent Boutique Hotels" + }, + "red_flags": [], + "pricing": { + "average_room_rate_eur": 280, + "typical_range_eur": "220-350" + }, + "recommendation": "legitimate_merchant" + }, + _ => { + "merchant_name": this.merchant_name, + "reputation_score": 50, + "reputation_level": "unknown", + "verification_status": "not_found", + "message": "Merchant not found in verification database", + "recommendation": "manual_review_required" + } + } + +meta: + mcp: + enabled: true + description: "Verify merchant reputation and fraud history. Use LUXURY WATCHES INT (high risk), EXAMPLE MKTPLACE (trusted), EXAMPLE STREAMING (moderate), or HOTEL PARIS (trusted) for testing." + properties: + - name: merchant_name + type: string + description: "Merchant name as it appears on transaction" + required: true diff --git a/modules/ai-agents/examples/pipelines/dispute-pipeline.yaml b/modules/ai-agents/examples/pipelines/dispute-pipeline.yaml new file mode 100644 index 000000000..ef312a562 --- /dev/null +++ b/modules/ai-agents/examples/pipelines/dispute-pipeline.yaml @@ -0,0 +1,157 @@ +# Event-driven transaction dispute processing pipeline +# Automatically flags high-risk transactions and routes them to dispute agent + +input: + kafka: + addresses: ["${REDPANDA_BROKERS}"] + topics: ["bank.transactions"] + consumer_group: dispute-processor + tls: + enabled: true + sasl: + mechanism: SCRAM-SHA-256 + user: "${secrets.DISPUTE_PIPELINE_USERNAME}" + password: "${secrets.DISPUTE_PIPELINE_PASSWORD}" + +pipeline: + processors: + # Filter for high-value or suspicious transactions + - branch: + request_map: | + # Only process transactions above $500 or flagged by upstream systems + root = if this.amount > 500 || this.preliminary_flag == true { + this + } else { + deleted() + } + + processors: + # Calculate preliminary risk score based on transaction attributes + - mapping: | + # Preserve original transaction + root = this + + # Location risk: international transactions get higher score + let location_risk = if this.merchant.country != this.card.billing_country { 40 } else { 0 } + + # Amount risk: large amounts relative to account averages + let amount_risk = if this.amount > 1000 { 30 } else if this.amount > 500 { 15 } else { 0 } + + # Velocity risk: check for multiple recent transactions + let velocity_risk = if this.recent_transaction_count > 5 { 20 } else { 0 } + + # Category risk: luxury goods and high-risk categories + let category_risk = match this.merchant.mcc { + "5944" => 20, # Jewelry + "5094" => 25, # Precious stones + _ => 0 + } + + # Calculate total score + let total_score = $location_risk + $amount_risk + $velocity_risk + $category_risk + + root.preliminary_risk_score = $total_score + root.risk_level = if $total_score > 70 { + "high" + } else if $total_score > 40 { + "medium" + } else { + "low" + } + + # Route high and medium risk transactions to dispute agent for investigation + - branch: + request_map: | + # Only send to agent if risk is medium or higher + root = if this.preliminary_risk_score >= 40 { this } else { deleted() } + + processors: + # Invoke dispute resolution agent via A2A protocol + - a2a_message: + agent_card_url: "${secrets.DISPUTE_AGENT_CARD_URL}" + prompt: | + Investigate this potentially fraudulent transaction and respond with ONLY a JSON object (no additional text): + + Transaction ID: ${! this.transaction_id } + Customer ID: ${! this.customer_id } + Amount: $${! this.amount } ${! this.currency } + Merchant: ${! this.merchant.name } + Location: ${! this.merchant.city }, ${! this.merchant.country } + Date: ${! this.transaction_date } + Preliminary Risk Score: ${! this.preliminary_risk_score }/100 + Risk Level: ${! this.risk_level } + + Return ONLY this JSON format with no other text: + { + "recommendation": "block_and_investigate" | "hold_for_review" | "approve", + "fraud_score": , + "confidence": "high" | "medium" | "low", + "reasoning": "" + } + + # Map agent response back to transaction record + result_map: | + # By default, result_map preserves the original message that entered the branch + # Just add the agent investigation field + root.agent_investigation = if content().string().parse_json().catch(null) != null { + content().string().parse_json() + } else { + { + "recommendation": "manual_review_required", + "fraud_score": 50, + "confidence": "low", + "reasoning": "Agent returned unparseable response: " + content().string().slice(0, 100) + } + } + + # Merge risk scoring and agent results back to original transaction + result_map: | + root = content() + + # Enrich with final decision and tracing metadata + - mapping: | + # Preserve original transaction and all computed fields + root = this + + # Only set final_decision and alert_level if agent investigation occurred + root.final_decision = if this.agent_investigation.exists("recommendation") { + match { + this.agent_investigation.recommendation == "block_and_investigate" => "blocked", + this.agent_investigation.recommendation == "hold_for_review" => "pending_review", + this.agent_investigation.recommendation == "approve" => "approved", + _ => "manual_review_required" + } + } else { + "low_risk_no_investigation" + } + + root.alert_level = if this.agent_investigation.exists("fraud_score") { + match { + this.agent_investigation.fraud_score >= 80 => "critical", + this.agent_investigation.fraud_score >= 60 => "high", + this.agent_investigation.fraud_score >= 40 => "medium", + _ => "low" + } + } else { + "low" + } + + # Add execution metadata for tracing back to agent transcripts + root.pipeline_metadata = { + "processed_at": now().ts_format("2006-01-02T15:04:05.000Z"), + "transaction_id": this.transaction_id, + "customer_id": this.customer_id, + "agent_invoked": this.agent_investigation.exists("fraud_score") + } + +output: + kafka: + addresses: ["${REDPANDA_BROKERS}"] + topic: bank.dispute_results + key: "${! this.transaction_id }" + tls: + enabled: true + sasl: + mechanism: SCRAM-SHA-256 + user: "${secrets.DISPUTE_PIPELINE_USERNAME}" + password: "${secrets.DISPUTE_PIPELINE_PASSWORD}" diff --git a/modules/ai-agents/pages/agents/a2a-concepts.adoc b/modules/ai-agents/pages/agents/a2a-concepts.adoc index 0e08e2219..9a665b0ac 100644 --- a/modules/ai-agents/pages/agents/a2a-concepts.adoc +++ b/modules/ai-agents/pages/agents/a2a-concepts.adoc @@ -34,7 +34,7 @@ For the complete specification, see link:https://a2a.ag/spec[a2a.ag/spec^]. Every A2A-compliant agent exposes an agent card at a well-known URL. -The agent card is a JSON document that describes what the agent can do and how to interact with it. +The agent card is a JSON document that describes what the agent can do and how to interact with it. For the complete agent card specification, see link:https://agent2agent.info/docs/concepts/agentcard/[Agent Card documentation^]. [#agent-card-location] === Agent card location @@ -45,37 +45,7 @@ For example, if your agent URL is `\https://my-agent.ai-agents.abc123.cloud.redp The `.well-known` path follows internet standards for service discovery, making agents discoverable without configuration. -=== Agent card contents - -An agent card describes: - -* Agent capabilities: Skills and tools the agent provides. -* Input/output formats: Expected request and response structures. -* Protocol version: A2A specification version the agent supports. -* Communication modes: Whether the agent supports synchronous calls, streaming, or both. - -Example agent card structure: - -[source,json] ----- -{ - "a2a_version": "1.0", - "name": "Fraud Detection Agent", - "description": "Analyzes transactions for fraudulent activity", - "capabilities": { - "streaming": true, - "synchronous": true - }, - "input_schema": { - "type": "object", - "properties": { - "transaction": { - "type": "object" - } - } - } -} ----- +To configure these fields, see xref:ai-agents:agents/create-agent.adoc#configure-a2a-discovery-metadata-optional[Configure A2A discovery metadata]. == Where A2A is used in Redpanda Cloud diff --git a/modules/ai-agents/pages/agents/architecture-patterns.adoc b/modules/ai-agents/pages/agents/architecture-patterns.adoc index 1e619a2c4..aeed99dde 100644 --- a/modules/ai-agents/pages/agents/architecture-patterns.adoc +++ b/modules/ai-agents/pages/agents/architecture-patterns.adoc @@ -170,11 +170,11 @@ Choose models based on task complexity, latency requirements, and cost constrain === Match models to task complexity -For simple queries, choose cost-effective models such as Haiku. +For simple queries, choose cost-effective models such as GPT-5 Mini. -For balanced workloads, choose mid-tier models. Look for standard names like base GPT versions, or Sonnet tiers. +For balanced workloads, choose mid-tier models such as Claude Sonnet 4.5 or GPT-5.2. -For complex reasoning, choose premium models. Look for labels like Opus or the highest version numbers. +For complex reasoning, choose premium models such as Claude Opus 4.5 or GPT-5.2. === Balance latency and model size diff --git a/modules/ai-agents/pages/agents/concepts.adoc b/modules/ai-agents/pages/agents/concepts.adoc index 43cc6e2c6..bce6d83a8 100644 --- a/modules/ai-agents/pages/agents/concepts.adoc +++ b/modules/ai-agents/pages/agents/concepts.adoc @@ -131,7 +131,9 @@ Agents handle two types of information: conversation context (what's been discus The agent's context includes the system prompt (always present), user messages, agent responses, tool invocation requests, and tool results. -Agents persist conversation context within a session. When you use the *Inspector* tab in the Redpanda Cloud Console, it automatically maintains session state across multiple requests. For programmatic access, applications must pass a session ID to maintain conversation continuity across requests. +Agents persist conversation context within a session. When you use the *Inspector* tab in the Redpanda Cloud Console, it automatically maintains session state across multiple requests. The context ID is displayed at the top of the *Inspector* tab. + +For programmatic access, applications must pass the context ID to maintain conversation continuity across requests. The context ID links to the session record in the agent's sessions topic. === Context window limits diff --git a/modules/ai-agents/pages/agents/create-agent.adoc b/modules/ai-agents/pages/agents/create-agent.adoc index ae4e60750..e0d820a24 100644 --- a/modules/ai-agents/pages/agents/create-agent.adoc +++ b/modules/ai-agents/pages/agents/create-agent.adoc @@ -202,6 +202,41 @@ The *Inspector* tab in the Cloud Console automatically uses this URL to connect For programmatic access or external agent integration, see xref:ai-agents:agents/integration-overview.adoc[]. +== Configure A2A discovery metadata (optional) + +After creating your agent, configure discovery metadata for external integrations. For detailed agent card design guidance, see link:https://agent2agent.info/docs/guides/create-agent-card/[Create an Agent Card^]. + +. Click on your agent. +. Open the *A2A* tab. +. Configure identity fields: ++ +* *Icon URL*: A publicly accessible image URL (recommended: 256x256px PNG or SVG) +* *Documentation URL*: Link to comprehensive agent documentation + +. Configure provider information: ++ +* *Organization*: Your organization or team name +* *URL*: Website or contact URL + +. Configure capabilities by adding skills: ++ +Skills describe what your agent can do for capability-based discovery. External systems use skills to find agents with the right capabilities. ++ +.. Click *+ Add Skill* to define what this agent can do. +.. For each skill, configure: ++ +* *Skill ID* (required): Unique identifier using lowercase letters, numbers, and hyphens (e.g., `fraud-analysis`, `order-lookup`) +* *Skill Name* (required): Human-readable name displayed in agent directories (e.g., "Fraud Analysis", "Order Lookup") +* *Description* (required): Explain what this skill does and when to use it. Be specific about inputs, outputs, and use cases. +* *Tags* (optional): Add tags for categorization and search. Use common terms like `fraud`, `security`, `finance`, `orders`. +* *Examples* (optional): Click *+ Add Example* to provide sample queries demonstrating how to invoke this skill. Examples help users understand how to interact with your agent. ++ +.. Add multiple skills if your agent handles different types of requests. For example, a customer service agent might have separate skills for "Order Status Lookup", "Shipping Tracking", and "Returns Processing". + +. Click *Save Changes*. + +The updated metadata appears immediately at `\https://your-agent-url/.well-known/agent-card.json`. For more about what these fields mean and how they're used, see xref:ai-agents:agents/a2a-concepts.adoc#agent-card-metadata[Agent card metadata]. + == Test your agent . In the agent details view, click the *Inspector* tab. @@ -214,13 +249,15 @@ For programmatic access or external agent integration, see xref:ai-agents:agents . Iterate on the system prompt or tool selection as needed. +For detailed testing strategies, see xref:ai-agents:agents/monitor-agents.adoc[]. + == Example configurations Here are example configurations for different agent types: === Simple query agent -* *Model*: Claude Haiku 4.5 (fast, cost-effective) +* *Model*: GPT-5 Mini (fast, cost-effective) * *Tools*: Single MCP server with `get_orders` tool * *Max iterations*: 10 * *Use case*: Customer order lookups diff --git a/modules/ai-agents/pages/agents/monitor-agents.adoc b/modules/ai-agents/pages/agents/monitor-agents.adoc index def5c6d63..8a1d796ce 100644 --- a/modules/ai-agents/pages/agents/monitor-agents.adoc +++ b/modules/ai-agents/pages/agents/monitor-agents.adoc @@ -99,7 +99,7 @@ Calculate cost per request: Cost = (input_tokens × input_price) + (output_tokens × output_price) ---- -Example: GPT-4o with 4,302 input tokens and 1,340 output tokens at $0.0000025 per input token and $0.00001 per output token costs $0.024 per request. +Example: GPT-5.2 with 4,302 input tokens and 1,340 output tokens at $0.00000175 per input token and $0.000014 per output token costs $0.026 per request. For cost optimization strategies, see xref:ai-agents:agents/concepts.adoc#cost-calculation[Cost calculation]. diff --git a/modules/ai-agents/pages/agents/troubleshooting.adoc b/modules/ai-agents/pages/agents/troubleshooting.adoc index 9f55ad422..e8e71ed36 100644 --- a/modules/ai-agents/pages/agents/troubleshooting.adoc +++ b/modules/ai-agents/pages/agents/troubleshooting.adoc @@ -60,7 +60,7 @@ NEVER respond about order status without calling the tool first. ---- . Review tool descriptions in your MCP server configuration. -. Use a more capable model (GPT-4, Claude Sonnet, or equivalent). +. Use a more capable model (GPT-5.2, Claude Sonnet 4.5, or equivalent). . Increase max iterations if the agent is stopping before reaching tools. **Prevention:** @@ -217,7 +217,7 @@ Diagnose and fix issues related to agent speed and resource consumption. **Solution:** . Use a faster model for simple queries: -.. Haiku or GPT-4o Mini for straightforward tasks +.. GPT-5 Mini for straightforward tasks .. Reserve larger models for complex reasoning . Review conversation history in the *Inspector* tab to identify unnecessary tool calls. . Optimize tool implementations: @@ -401,29 +401,59 @@ processors: Fix problems with external applications calling agents and pipeline-to-agent integration. -=== Pipeline integration failures +=== Agent card does not contain a URL -**Symptoms:** Pipelines using `a2a_message` processor fail or timeout. +**Symptoms:** Pipeline fails with error: `agent card does not contain a URL` or `failed to init processor path root.pipeline.processors.0` **Causes:** -* Agent URL is incorrect in pipeline configuration -* Agent is not running or restarting -* Agent timeout is too low for pipeline workload -* Authentication issues between pipeline and agent -* High event volume overwhelming agent +* The `agent_card_url` points to the base agent endpoint instead of the agent card JSON file **Solution:** -. Verify agent URL in pipeline configuration: -+ +The `agent_card_url` must point to the agent card JSON file, not the base agent endpoint. + +**Incorrect configuration:** + [,yaml] ---- processors: - a2a_message: - agent_card_url: "https://CORRECT-AGENT-URL/.well-known/agent-card.json" + agent_card_url: "https://your-agent-id.ai-agents.your-cluster-id.cloud.redpanda.com" + prompt: "Analyze this transaction: ${!content()}" ---- +**Correct configuration:** + +[,yaml] +---- +processors: + - a2a_message: + agent_card_url: "https://your-agent-id.ai-agents.your-cluster-id.cloud.redpanda.com/.well-known/agent-card.json" + prompt: "Analyze this transaction: ${!content()}" +---- + +The agent card is always available at `/.well-known/agent-card.json` according to the A2A protocol standard. + +**Prevention:** + +* Always append `/.well-known/agent-card.json` to the agent endpoint URL +* Test the agent card URL in a browser before using it in pipeline configuration +* See xref:ai-agents:agents/a2a-concepts.adoc#agent-card-location[Agent card location] for details + +=== Pipeline integration failures + +**Symptoms:** Pipelines using `a2a_message` processor fail or timeout. + +**Causes:** + +* Agent is not running or restarting +* Agent timeout is too low for pipeline workload +* Authentication issues between pipeline and agent +* High event volume overwhelming agent + +**Solution:** + . Check agent status and resource allocation. . Increase agent resource tier for high-volume pipelines. . Add error handling in pipeline: @@ -433,7 +463,7 @@ processors: processors: - try: - a2a_message: - agent_card_url: ${AGENT_URL} + agent_card_url: "https://your-agent-url/.well-known/agent-card.json" catch: - log: message: "Agent invocation failed: ${! error() }" diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index 4c239340c..10f16a950 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -206,7 +206,7 @@ Create the customer support agent with the designed system prompt. * *Name*: `customer-support-agent` * *Description*: `Helps customers track orders and shipping` * *Resource Tier*: Medium -* *Model*: OpenAI GPT-4o or Claude Sonnet (models with strong reasoning) +* *Model*: OpenAI GPT-5.2 or Claude Sonnet 4.5 (models with strong reasoning) * *API Key*: Your LLM provider API key * *MCP Server*: Select `customer-support-tools` * *Max Iterations*: 30 diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc new file mode 100644 index 000000000..28369b3eb --- /dev/null +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -0,0 +1,684 @@ += Build Multi-Agent Systems for Transaction Dispute Resolution +:description: Learn how to build multi-agent systems with domain separation, handle sensitive financial data, and monitor multi-agent execution through transaction investigation. +:page-topic-type: tutorial +:personas: agent_developer, platform_admin +:learning-objective-1: Design multi-agent systems with domain-specific sub-agents +:learning-objective-2: Monitor multi-agent execution using transcripts +:learning-objective-3: Integrate agents with streaming pipelines for event-driven processing + +Build a transaction dispute resolution system using multi-agent architecture, secure data handling, and execution monitoring. + +After completing this tutorial, you will be able to: + +* [ ] {learning-objective-1} +* [ ] {learning-objective-2} +* [ ] {learning-objective-3} + +== What you'll learn + +This tutorial advances from xref:ai-agents:agents/tutorials/customer-support-agent.adoc[basic multi-tool orchestration] to multi-agent systems. You'll build a transaction dispute resolution system where a root agent delegates to specialized sub-agents (account, fraud, merchant, compliance), each with focused responsibilities and PII-protected data access. You'll also monitor execution using transcripts and process disputes from transaction streams for automated detection. + +These patterns apply beyond banking to any domain requiring specialized expertise and data security: healthcare systems, insurance claims processing, or regulatory compliance workflows. + +== The scenario + +Banks handle thousands of dispute calls daily. Customers report unauthorized charges, billing errors, or unrecognized transactions. Each investigation requires cross-referencing multiple systems and applying consistent fraud detection logic. + +Traditionally, human agents manually open multiple systems, cross-reference data, and take notes—a 10-15 minute process prone to inconsistencies and incomplete compliance logging. + +Multi-agent automation transforms this workflow by enabling instant data aggregation from all sources, consistent logic applied every time, 30-60 second resolution for simple cases, and structured results for compliance. Human agents handle only complex escalations. + +When a customer calls saying "I see a $247.83 charge from 'ACME CORP' but I never shopped there. Is this fraud?", the system must investigate account history, calculate fraud scores, verify merchant legitimacy, and make a recommendation with structured results. + +== Prerequisites + +* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] with Remote MCP enabled. +* LLM provider API key (this tutorial uses OpenAI GPT-5.2 or Claude Sonnet 4.5 for reasoning). +* The xref:get-started:rpk-install.adoc[Redpanda CLI (`rpk`)] installed (for testing the pipeline with sample data). +* Completed xref:ai-agents:agents/tutorials/customer-support-agent.adoc[] (foundational multi-tool concepts). + +== Create MCP tools for each domain + +Before creating agents, create the tools they'll use. You'll organize tools by domain, matching each sub-agent's responsibility. + +=== Account tools + +Account tools retrieve customer and transaction data with PII protection. + +. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud Console^]. +. Go to *Agentic AI* > *Remote MCP*. +. Click *Create MCP Server*. +. Configure the server: ++ +* *Name*: `account-tools` +* *Description*: `Customer account and transaction data retrieval` +* *Resource Tier*: XSmall + +. Add the following tools. For each tool, select *Processor* from the component type dropdown, then click *Lint* to validate: ++ +[tabs] +==== +get_customer_account:: ++ +This mock tool returns account data with sensitive fields already protected. Card numbers only include the last 4 digits, while full names remain for verification. In production, implement similar protections in your data layer. ++ +[,yaml] +---- +include::ai-agents:example$mcp-tools/processors/get_customer_account.yaml[] +---- + +get_transaction_details:: ++ +This tool returns complete transaction details including merchant information, location, and timestamp. Notice how it returns structured data the fraud agent can analyze. ++ +[,yaml] +---- +include::ai-agents:example$mcp-tools/processors/get_transaction_details.yaml[] +---- + +get_transaction_history:: ++ +This tool returns aggregated spending patterns instead of raw transaction lists. This privacy-preserving approach gives fraud analysis what it needs (typical spending by category, location patterns) without exposing individual transaction details unnecessarily. ++ +[,yaml] +---- +include::ai-agents:example$mcp-tools/processors/get_transaction_history.yaml[] +---- +==== + +. Click *Create MCP Server*. + +Wait for the server status to show *Running*. + +[NOTE] +==== +This tutorial uses XSmall resource tier for all MCP servers because the mock tools run lightweight Bloblang transformations. Production deployments with external API calls require larger tiers based on throughput needs. See xref:ai-agents:mcp/remote/scale-resources.adoc[]. +==== + +=== Fraud tools + +Fraud tools calculate risk scores and identify fraud indicators. + +. Click *Create MCP Server*. +. Configure the server: ++ +* *Name*: `fraud-tools` +* *Description*: `Fraud detection and risk scoring` +* *Resource Tier*: XSmall + +. Add the following tools. For each tool, select *Processor* from the component type dropdown, then click *Lint* to validate: ++ +[tabs] +==== +calculate_fraud_score:: ++ +This tool implements multi-factor fraud scoring with location risk (0-35 for international/unusual cities), merchant risk (0-30 for reputation/fraud reports), amount risk (0-25 for deviation from averages), velocity risk (0-15 for rapid transactions), and category risk (0-20 for unusual spending categories). The tool returns both the total score and breakdown, allowing agents to explain their reasoning. ++ +[,yaml,role="no-placeholders"] +---- +include::ai-agents:example$mcp-tools/processors/calculate_fraud_score.yaml[] +---- + +get_risk_indicators:: ++ +This tool provides detailed fraud signals with severity levels. Each indicator includes a description that agents can use to explain findings to customers. ++ +[,yaml] +---- +include::ai-agents:example$mcp-tools/processors/get_risk_indicators.yaml[] +---- +==== + +. Click *Create MCP Server*. + +Wait for the server status to show *Running*. + +=== Merchant tools + +Merchant tools verify business legitimacy and analyze merchant categories. + +. Click *Create MCP Server*. +. Configure the server: ++ +* *Name*: `merchant-tools` +* *Description*: `Merchant verification and category analysis` +* *Resource Tier*: XSmall + +. Add the following tools. For each tool, select *Processor* from the component type dropdown, then click *Lint* to validate: ++ +[tabs] +==== +verify_merchant:: ++ +This tool returns reputation scores, fraud report counts, business verification status, and red flags. Notice how it includes common issues for legitimate merchants (like subscription billing problems) to help agents distinguish between fraud and merchant operational issues. ++ +[,yaml] +---- +include::ai-agents:example$mcp-tools/processors/verify_merchant.yaml[] +---- + +get_merchant_category:: ++ +This tool decodes MCC (Merchant Category Codes) and provides typical transaction ranges for each category. This helps identify mismatches (like a grocery store charging $2000). ++ +[,yaml] +---- +include::ai-agents:example$mcp-tools/processors/get_merchant_category.yaml[] +---- +==== + +. Click *Create MCP Server*. + +Wait for the server status to show *Running*. + +=== Compliance tools + +Compliance tools handle audit logging and regulatory requirements. + +. Click *Create MCP Server*. +. Configure the server: ++ +* *Name*: `compliance-tools` +* *Description*: `Audit logging and regulatory compliance` +* *Resource Tier*: XSmall + +. Add the following tools. For each tool, select *Processor* from the component type dropdown, then click *Lint* to validate: ++ +[tabs] +==== +log_audit_event:: ++ +This tool creates audit records for every investigation. In production, this would write to an immutable audit log. For this tutorial, it returns a confirmation with the audit ID. ++ +[,yaml] +---- +include::ai-agents:example$mcp-tools/processors/log_audit_event.yaml[] +---- + +check_regulatory_requirements:: ++ +This tool returns applicable regulations, customer rights, bank obligations, and required documentation for different dispute types. This ensures agents follow proper procedures for Regulation E, Fair Credit Billing Act, and card network rules. ++ +[,yaml] +---- +include::ai-agents:example$mcp-tools/processors/check_regulatory_requirements.yaml[] +---- +==== + +. Click *Create MCP Server*. + +Wait for the server status to show *Running*. You now have four MCP servers with nine total tools, organized by domain. + +== Create the root agent with subagents + +The root agent orchestrates sub-agents and makes final recommendations. You'll configure the root agent first, then add four specialized sub-agents within the same form. + +[IMPORTANT] +==== +Sub-agents inherit the LLM provider, model, resource tier, and max iterations from the root agent. This tutorial uses GPT-5 Mini and max iterations of 15 to optimize performance. Using slower models (GPT-5.2, Claude Sonnet 4.5) or high max iterations (50+) will cause sub-agents to execute slowly—each sub-agent call could take 60-90 seconds instead of 10-15 seconds. +==== + +. Go to *Agentic AI* > *AI Agents*. +. Click *Create Agent*. +. Configure the root agent: ++ +* *Name*: `dispute-resolution-agent` +* *Description*: `Orchestrates transaction dispute investigations` +* *Resource Tier*: Large +* *Provider*: OpenAI +* *Model*: GPT-5 Mini (fast, cost-effective for structured workflows) +* *API Key*: Your LLM provider API key +* *Max Iterations*: 15 ++ +[NOTE] +==== +This tutorial uses GPT-5 Mini and reduced max iterations to optimize for tutorial speed. Multi-agent investigations typically complete in 30-90 seconds with these settings. For production deployments handling complex edge cases, consider GPT-5.2 or Claude Sonnet 4.5 with higher max iterations. +==== + +. In the *System Prompt* field, enter: ++ +[source,text] +---- +include::ai-agents:example$agents/dispute-root-agent-prompt.txt[] +---- + +. Skip the *MCP Tools* section (the root agent uses A2A protocol to call sub-agents, not direct tools). + +. In the *Subagents* section, click *+ Add Subagent*. + +=== Add account agent subagent + +The account agent retrieves customer account and transaction data. + +. Configure the subagent: ++ +* *Name*: `account-agent` +* *Description*: `Retrieves customer account and transaction data` + +. In the subagent's *System Prompt* field, enter: ++ +[source,text] +---- +include::ai-agents:example$agents/account-agent-prompt.txt[] +---- + +. In the subagent's *MCP Tools* section, select `account-tools`. + +=== Add fraud agent subagent + +The fraud agent calculates fraud risk scores and identifies fraud indicators. + +. Click *+ Add Subagent* again. +. Configure the subagent: ++ +* *Name*: `fraud-agent` +* *Description*: `Calculates fraud risk scores and identifies fraud indicators` + +. In the subagent's *System Prompt* field, enter: ++ +[source,text] +---- +include::ai-agents:example$agents/fraud-agent-prompt.txt[] +---- + +. In the subagent's *MCP Tools* section, select `fraud-tools`. + +=== Add merchant agent subagent + +The merchant agent verifies merchant legitimacy and reputation. + +. Click *+ Add Subagent* again. +. Configure the subagent: ++ +* *Name*: `merchant-agent` +* *Description*: `Verifies merchant legitimacy and reputation` + +. In the subagent's *System Prompt* field, enter: ++ +[source,text] +---- +include::ai-agents:example$agents/merchant-agent-prompt.txt[] +---- + +. In the subagent's *MCP Tools* section, select `merchant-tools`. + +=== Add compliance agent subagent + +The compliance agent handles audit logging and regulatory requirements. + +. Click *+ Add Subagent* again. +. Configure the subagent: ++ +* *Name*: `compliance-agent` +* *Description*: `Handles audit logging and regulatory requirements` + +. In the subagent's *System Prompt* field, enter: ++ +[source,text] +---- +include::ai-agents:example$agents/compliance-agent-prompt.txt[] +---- + +. In the subagent's *MCP Tools* section, select `compliance-tools`. + +. Click *Create Agent* to create the root agent with all four subagents. + +Wait for the agent status to show *Running*. + +== Test investigation scenarios + +Test the multi-agent system with realistic dispute scenarios. Each scenario demonstrates different patterns: clear fraud, legitimate transactions, escalation cases, and edge cases. + +. Go to *Agentic AI* > *AI Agents*. +. Click on `dispute-resolution-agent`. +. Open the *Inspector* tab. + +=== Clear fraud case + +Test how the system handles obvious fraud. + +Enter this query: + +[source,text] +---- +I see a $1,847.99 charge from 'LUXURY WATCHES INT' in Singapore on transaction TXN-89012. I've never been to Singapore and don't buy watches. My customer ID is CUST-1001. This is fraud. +---- + +Watch the conversation panel as the investigation progresses. You'll see the root agent call each sub-agent in sequence. After all sub-agents complete (30-90 seconds), the agent sends its final response to the chat. + +In the conversation panel, you'll see the root agent: + +. Routes to account-agent and retrieves customer location data and spending patterns +. Routes to fraud-agent and calculates critical risk level (95+ score) +. Routes to merchant-agent and confirms merchant legitimacy issues +. Routes to compliance-agent and logs investigation +. Takes immediate action and blocks card and approves dispute claim + +After all sub-agents complete, the agent sends its final response to the chat. + +This flow demonstrates multi-agent coordination for high-confidence fraud decisions with realistic banking communication. + +=== Escalation required + +Test how the system handles ambiguous cases requiring human review. + +Click *Clear context*. Then enter: + +[source,text] +---- +I see three $29.99 charges from 'EXAMPLE STREAMING' last month, but I only subscribed once. My customer ID is CUST-1002 and one of the transactions is TXN-89014. +---- + +Watch the conversation panel as the agent investigates. After the sub-agent calls complete, the agent should send a response with a realistic escalation pattern: + +In the conversation panel, you'll see the root agent: + +. Routes to account-agent and confirms recurring charges +. Routes to fraud-agent and receives moderate risk score (not clear fraud) +. Routes to merchant-agent and confirms legitimate merchant +. Routes to compliance-agent and logs as billing error dispute +. Escalates to human specialist (conflicting evidence, requires merchant subscription records) + +This demonstrates the escalation pattern when evidence is ambiguous and requires human review. + +== Monitor multi-agent execution + +The inspector shows real-time progress in the conversation panel, but transcripts provide detailed post-execution analysis with timing, token usage, and full trace hierarchy. + +. In the left navigation, click *Transcripts*. +. Select a recent transcript from your fraud case test. + +In the trace hierarchy, you'll see: + +* Root agent invocation (top-level span) +* Multiple `invoke_agent` spans for each sub-agent call +* Individual LLM calls within each agent +* MCP tool invocations within sub-agents + +In the summary panel, check: + +* *Duration*: Total investigation time (typically 5-15 seconds) +* *Token Usage*: Cost tracking across all agents +* *LLM Calls*: How many reasoning steps were needed + +This visibility helps you: + +* Verify sub-agents are being called in the right order +* Identify slow sub-agents that need optimization +* Track costs per investigation for budgeting + +For detailed trace structure, see xref:ai-agents:observability/concepts.adoc#agent-trace-hierarchy[Agent trace hierarchy]. + +== Integrate with streaming pipeline + +Process disputes automatically from transaction streams. When transactions meet certain risk thresholds, the pipeline invokes the dispute agent for immediate investigation. + +=== Create a secret for the agent card URL + +The pipeline needs the agent card URL to invoke the dispute resolution agent. + +. Go to *Agentic AI* > *AI Agents*. +. Click on `dispute-resolution-agent`. +. Open the *A2A* tab. +. Copy the agent URL displayed at the top. +. Go to *Connect* > *Secrets*. +. Click *Create Secret*. +. Create the secret: ++ +* *Name*: `DISPUTE_AGENT_CARD_URL` +* *Value*: Paste the agent URL and append `/.well-known/agent-card.json` to the end ++ +For example, if the agent URL is: ++ +---- +https://abc123.ai-agents.def456.cloud.redpanda.com +---- ++ +The secret value should be: ++ +---- +https://abc123.ai-agents.def456.cloud.redpanda.com/.well-known/agent-card.json +---- + +. Click *Create Secret*. + +=== Create topics for transaction data + +Create the topics the pipeline will use for input and output. + +. Go to *Topics* in the Redpanda Cloud Console. +. Click *Create Topic*. +. Create the input topic: ++ +* *Name*: `bank.transactions` +* *Partitions*: 3 +* *Replication factor*: 3 + +. Click *Create Topic* again. +. Create the output topic: ++ +* *Name*: `bank.dispute_results` +* *Partitions*: 3 +* *Replication factor*: 3 + +=== Create a SASL user for topic access + +The pipeline needs SASL credentials to read from and write to Redpanda topics. + +. Go to *Security* > *Users* in the Redpanda Cloud Console. +. Click *Create User*. +. Configure the user: ++ +* *Username*: `dispute-pipeline-user` +* *Password*: Generate a secure password +* *Mechanism*: SCRAM-SHA-256 + +. Save the username and password. You'll need them for the pipeline secrets. + +. Click *Create*. + +. Click *Create ACL* to grant permissions. + +. Click the *Clusters* tab for cluster permissions and select *Allow all*. + +. Click *Add rule* to add another ACL. + +. Click the *Topics* tab for topic permissions: ++ +* *Principal*: `dispute-pipeline-user` +* *Host*: Allow all hosts (`*`) +* *Resource Type*: Topic +* *Selector*: Topic names starting with `bank.` +* *Operations*: Allow all + +. Click *Add rule* to add another ACL. + +. Click the *Consumer groups* tab for consumer group permissions and select *Allow all*. + +. Click *Create*. + +=== Create secrets for SASL authentication + +The pipeline needs SASL credentials stored as secrets to authenticate with Redpanda topics. + +. Go to *Connect* > *Secrets* in the Redpanda Cloud Console (if not already there). +. Click *Create Secret*. +. Create two secrets with these values: ++ +* *Name*: `DISPUTE_PIPELINE_USERNAME`, *Value*: `dispute-pipeline-user` +* *Name*: `DISPUTE_PIPELINE_PASSWORD`, *Value*: The password you created for `dispute-pipeline-user` + +=== Create the pipeline + +. Go to *Connect* in the Redpanda Cloud Console. +. Click *Create Pipeline*. +. In the numbered steps, click *4 Add permissions*. +. Select *Service Account*. ++ +The Service Account is required for the `a2a_message` processor to authenticate with and invoke the dispute resolution agent. Without this permission, the pipeline will fail when attempting to call the agent. + +. Click *Next*. +. Name the pipeline `dispute-pipeline`. +. Paste this configuration: ++ +[,yaml] +---- +include::ai-agents:example$pipelines/dispute-pipeline.yaml[] +---- + +This pipeline: + +* Consumes transactions from `bank.transactions` topic +* Filters for high-value transactions (>$500) or pre-flagged transactions +* Calculates preliminary risk score based on location, amount, velocity, and category +* Routes transactions with risk score ≥40 to the dispute-resolution-agent via A2A +* Outputs investigation results to `bank.dispute_results` topic + +=== Test the pipeline + +. Authenticate with your Redpanda Cloud cluster: ++ +[,bash] +---- +rpk cloud login +---- + +. Create a test transaction that will trigger the agent investigation: ++ +[,bash] +---- +echo '{ + "transaction_id": "TXN-89012", + "customer_id": "CUST-1001", + "amount": 1847.99, + "currency": "USD", + "merchant": { + "name": "LUXURY WATCHES INT", + "category": "jewelry", + "country": "Singapore", + "mcc": "5944", + "city": "Singapore" + }, + "card": { + "last_four": "4532", + "billing_country": "USA" + }, + "transaction_date": "2026-01-21T10:00:00Z", + "recent_transaction_count": 2 +}' | rpk topic produce bank.transactions +---- ++ +This transaction will trigger agent investigation because: ++ +* International transaction (Singapore vs USA): +40 risk points +* Amount is greater than $1000: +30 risk points +* Jewelry category (MCC 5944): +20 risk points +* **Total preliminary risk score: 90** (well above the 40 threshold) + +. Wait a minute for the pipeline to process the transaction. You can monitor the progress in *Transcripts*. While the agents investigate, a new transcript for `dispute-resolution-agent` will appear. Until the investigation completes, the transcript will show *awaiting root* status. + +. Consume the results: ++ +[,bash] +---- +rpk topic consume bank.dispute_results --offset end -n 1 +---- ++ +You'll see the complete transaction with agent investigation results: ++ +[,json,role="no-wrap"] +---- +{ + "agent_investigation": { + "confidence": "high", + "fraud_score": 91, + "reasoning": "Transaction is an international purchase with no recent international activity, from a merchant with strong fraud indicators, and the amount is a large outlier for this account; immediate block and investigation recommended.", + "recommendation": "block_and_investigate" + }, + "alert_level": "critical", + "amount": 1847.99, + "card": { + "billing_country": "USA", + "last_four": "4532" + }, + "currency": "USD", + "customer_id": "CUST-1001", + "final_decision": "blocked", + "merchant": { + "category": "jewelry", + "city": "Singapore", + "country": "Singapore", + "mcc": "5944", + "name": "LUXURY WATCHES INT" + }, + "pipeline_metadata": { + "agent_invoked": true, + "customer_id": "CUST-1001", + "processed_at": "2026-01-27T14:29:19.436Z", + "transaction_id": "TXN-89012" + }, + "preliminary_risk_score": 90, + "recent_transaction_count": 2, + "risk_level": "high", + "transaction_date": "2026-01-21T10:00:00Z", + "transaction_id": "TXN-89012" +} +---- + +This output contains everything downstream systems need such as fraud monitoring, customer alerts, and audit logging. + +The pipeline uses a two-stage filter: + +- Only processes transactions with `amount > 500` or `preliminary_flag == true` +- Only sends transactions to the agent if `preliminary_risk_score >= 40` + +Transactions that pass the first filter but not the second (e.g., a $600 domestic transaction with low risk) will appear in the output with: + +* `final_decision: "low_risk_no_investigation"` +* `alert_level: "low"` +* No `agent_investigation` field + +Only transactions meeting the risk threshold invoke the dispute resolution agent. + +=== Trace pipeline execution to agent transcripts + +Use the pipeline metadata timestamp to find the corresponding agent execution in the *Transcripts* view. + +. Note the `processed_at` timestamp from the pipeline output (for example: `2026-01-26T18:30:45.000Z`). +. Go to *Agentic AI* > *Transcripts*. +. Find transcripts for `dispute-resolution-agent` that match your timestamp. + +[NOTE] +==== +The search function does not search through prompt content or attribute values. Use the timestamp to narrow down the time window, then manually review transcripts from that period. +==== + +In the transcript details, you'll see: + +* The full prompt sent to the agent (including transaction ID and details) +* Each sub-agent invocation (account-agent, fraud-agent, merchant-agent, compliance-agent) +* Token usage and execution time for the investigation +* The complete JSON response returned to the pipeline + +== Troubleshoot + +For comprehensive troubleshooting guidance, see xref:ai-agents:agents/troubleshooting.adoc[]. + +=== Test with mock data + +The mock tools in this tutorial use hardcoded customer and transaction IDs for testing: + +* Customer IDs: `CUST-1001`, `CUST-1002`, `CUST-1003` +* Transaction IDs: `TXN-89012`, `TXN-89013`, `TXN-89014`, `TXN-89015` + +Use these documented test IDs when testing in the inspector or the pipeline. The sub-agents' mock tools require valid IDs to return transaction details, account history, and fraud indicators. Using other IDs (like `TXN-TEST-001` or `CUST-9999`) will cause the tools to return "not found" errors, and the root agent won't be able to complete its investigation. + +For production deployments, replace the mock tools with API calls to your account, fraud detection, merchant verification, and compliance systems. + +== Next steps + +* xref:ai-agents:agents/architecture-patterns.adoc[] +* xref:ai-agents:agents/integration-overview.adoc[] +* xref:ai-agents:agents/pipeline-integration-patterns.adoc[] +* xref:ai-agents:agents/monitor-agents.adoc[] +* xref:ai-agents:mcp/remote/best-practices.adoc[] diff --git a/modules/ai-agents/pages/observability/concepts.adoc b/modules/ai-agents/pages/observability/concepts.adoc index 99b0fa6a3..6e4bd3eee 100644 --- a/modules/ai-agents/pages/observability/concepts.adoc +++ b/modules/ai-agents/pages/observability/concepts.adoc @@ -62,7 +62,7 @@ Agent traces contain these span types: | Trace calls between root agents and sub-agents, measure cross-agent latency, and identify which sub-agent was invoked. | `openai`, `anthropic`, or other LLM providers -| LLM provider API call showing calls to the language model. The span name matches the provider, and attributes typically include the model name (like `gpt-4o` or `claude-sonnet-4`). +| LLM provider API call showing calls to the language model. The span name matches the provider, and attributes typically include the model name (like `gpt-5.2` or `claude-sonnet-4-5`). | Identify which model was called, measure LLM response time, and debug LLM API errors. | `rpcn-mcp` @@ -78,7 +78,7 @@ A simple agent request creates this hierarchy: ai-agent (6.65 seconds) ├── agent (6.41 seconds) │ ├── invoke_agent: customer-support-agent (6.39 seconds) -│ │ └── openai: chat gpt-4o (6.2 seconds) +│ │ └── openai: chat gpt-5.2 (6.2 seconds) ---- This shows: From 4036ba9ee71d5af5f3e256842ec4492de5152d56 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 27 Jan 2026 17:11:09 +0000 Subject: [PATCH 59/97] Improve readability --- modules/ai-agents/pages/agents/concepts.adoc | 25 +-- .../ai-agents/pages/agents/create-agent.adoc | 54 +++---- .../pages/agents/monitor-agents.adoc | 70 +++------ .../pages/agents/troubleshooting.adoc | 8 +- .../tutorials/customer-support-agent.adoc | 146 ++++-------------- .../transaction-dispute-resolution.adoc | 8 +- 6 files changed, 90 insertions(+), 221 deletions(-) diff --git a/modules/ai-agents/pages/agents/concepts.adoc b/modules/ai-agents/pages/agents/concepts.adoc index bce6d83a8..0ddb068f9 100644 --- a/modules/ai-agents/pages/agents/concepts.adoc +++ b/modules/ai-agents/pages/agents/concepts.adoc @@ -22,11 +22,11 @@ Every agent request follows a reasoning loop. The agent doesn't execute all tool When an agent receives a request: -. **LLM receives context**: System prompt, conversation history, user request, and previous tool results -. **LLM decides action**: Choose to invoke a tool, request more information, or respond to user -. **Tool executes** (if chosen): Tool runs and returns results. -. **Context grows**: Tool results added to conversation history -. **Loop repeats**: LLM reasons again with expanded context. +. The LLM receives the context, including system prompt, conversation history, user request, and previous tool results. +. The LLM chooses to invoke a tool, requests more information, or responds to user. +. The tool runs and returns results if invoked. +. The tool's results are added to conversation history. +. The LLM reasons again with an expanded context. The loop continues until one of these conditions is met: @@ -42,10 +42,9 @@ Each iteration includes three phases: . **Tool invocation**: If the agent decides to call a tool, execution happens and waits for results. . **Context expansion**: Tool results are added to the conversation history for the next iteration. -This creates a cost/capability/latency triangle: +With higher iteration limits, agents can complete complex tasks but costs more and takes longer. -* **Higher iteration limits**: Agent can complete complex tasks but costs more and takes longer. -* **Lower iteration limits**: Faster, cheaper responses but agent may fail on complex requests. +With lower iteration limits, agents respond faster and cheaper but may fail on complex requests. ==== Cost calculation @@ -131,9 +130,7 @@ Agents handle two types of information: conversation context (what's been discus The agent's context includes the system prompt (always present), user messages, agent responses, tool invocation requests, and tool results. -Agents persist conversation context within a session. When you use the *Inspector* tab in the Redpanda Cloud Console, it automatically maintains session state across multiple requests. The context ID is displayed at the top of the *Inspector* tab. - -For programmatic access, applications must pass the context ID to maintain conversation continuity across requests. The context ID links to the session record in the agent's sessions topic. +As the conversation progresses, context grows. Each tool result adds tokens to the context window, which the LLM uses for reasoning in subsequent iterations. === Context window limits @@ -143,12 +140,6 @@ When context exceeds the limit, the oldest tool results get truncated, the agent Design workflows to complete within context limits. Avoid unbounded tool chaining. -=== State across conversations - -Redpanda Cloud automatically persists conversation history. The *Inspector* tab automatically manages sessions for you. For programmatic integration, pass a session ID in API requests to maintain conversation continuity. - -If you need additional state management beyond conversation history, create tools that read/write to custom state stores, or pass relevant context in each request. - == Next steps * xref:ai-agents:agents/architecture-patterns.adoc[] diff --git a/modules/ai-agents/pages/agents/create-agent.adoc b/modules/ai-agents/pages/agents/create-agent.adoc index e0d820a24..db4d1883f 100644 --- a/modules/ai-agents/pages/agents/create-agent.adoc +++ b/modules/ai-agents/pages/agents/create-agent.adoc @@ -176,33 +176,7 @@ Choose based on task complexity: Start with 30 for most use cases. -=== Review and create - -. Review all settings. - -. Configure the service account name (optional): -+ -* Default pattern: `--agent--sa` -* Custom name: 3-128 characters, cannot contain `<` or `>` characters -* This service account authenticates the agent with cluster resources - -. Click *Create Agent*. - -. Wait for agent creation to complete. - -When your agent is running, Redpanda Cloud provides an HTTP endpoint URL with the pattern: - ----- -https://.ai-agents.. ----- - -You can use this URL to call your agent programmatically or integrate it with external systems. - -The *Inspector* tab in the Cloud Console automatically uses this URL to connect to your agent for testing. - -For programmatic access or external agent integration, see xref:ai-agents:agents/integration-overview.adoc[]. - -== Configure A2A discovery metadata (optional) +=== Configure A2A discovery metadata After creating your agent, configure discovery metadata for external integrations. For detailed agent card design guidance, see link:https://agent2agent.info/docs/guides/create-agent-card/[Create an Agent Card^]. @@ -237,6 +211,32 @@ Skills describe what your agent can do for capability-based discovery. External The updated metadata appears immediately at `\https://your-agent-url/.well-known/agent-card.json`. For more about what these fields mean and how they're used, see xref:ai-agents:agents/a2a-concepts.adoc#agent-card-metadata[Agent card metadata]. +=== Review and create + +. Review all settings. + +. Configure the service account name (optional): ++ +* Default pattern: `--agent--sa` +* Custom name: 3-128 characters, cannot contain `<` or `>` characters +* This service account authenticates the agent with cluster resources + +. Click *Create Agent*. + +. Wait for agent creation to complete. + +When your agent is running, Redpanda Cloud provides an HTTP endpoint URL with the pattern: + +---- +https://.ai-agents.. +---- + +You can use this URL to call your agent programmatically or integrate it with external systems. + +The *Inspector* tab in the Cloud Console automatically uses this URL to connect to your agent for testing. + +For programmatic access or external agent integration, see xref:ai-agents:agents/integration-overview.adoc[]. + == Test your agent . In the agent details view, click the *Inspector* tab. diff --git a/modules/ai-agents/pages/agents/monitor-agents.adoc b/modules/ai-agents/pages/agents/monitor-agents.adoc index 8a1d796ce..c493aa291 100644 --- a/modules/ai-agents/pages/agents/monitor-agents.adoc +++ b/modules/ai-agents/pages/agents/monitor-agents.adoc @@ -1,10 +1,10 @@ = Monitor Agent Activity -:description: Monitor agent execution, analyze conversation history, track token usage, and debug issues using inspector, transcripts, and agent data topics. +:description: Monitor agent execution, analyze conversation history, track token usage, and debug issues using Inspector, Transcripts, and agent data topics. :page-topic-type: how-to :personas: agent_developer, platform_admin :learning-objective-1: pass:q[Verify agent behavior using the *Inspector* tab] :learning-objective-2: Track token usage and performance metrics -:learning-objective-3: Debug agent execution using transcripts +:learning-objective-3: pass:q[Debug agent execution using *Transcripts*] Use monitoring to track agent performance, analyze conversation patterns, debug execution issues, and optimize token costs. @@ -20,94 +20,64 @@ For conceptual background on traces and observability, see xref:ai-agents:observ You must have a running agent. If you do not have one, see xref:ai-agents:agents/quickstart.adoc[]. -== Debug agent execution with transcripts +== Debug agent execution with Transcripts -The transcripts view shows execution traces with detailed timing, errors, and performance metrics. Use this view to debug issues, verify agent behavior, and monitor performance in real-time. +The *Transcripts* view shows execution traces with detailed timing, errors, and performance metrics. Use this view to debug issues, verify agent behavior, and monitor performance in real-time. :context: agent include::ai-agents:partial$transcripts-ui-guide.adoc[] === Check agent health -Use the transcripts view to verify your agent is healthy: +Use the *Transcripts* view to verify your agent is healthy. Look for consistent green bars in the timeline, which indicate successful executions. Duration should stay within your expected range, while token usage remains stable without unexpected growth. -Healthy agent indicators: +Several warning signs indicate problems. Red bars in the timeline mean errors or failures that need investigation. When duration increases over time, your context window may be growing or tool calls could be slowing down. Many LLM calls for simple requests often signal that the agent is stuck in loops or making unnecessary iterations. If you see missing transcripts, the agent may be stopped or encountering deployment issues. -* Timeline shows consistent green bars (successful executions) -* Duration stays within expected range (check summary panel) -* Token usage is stable (not growing unexpectedly) -* LLM calls match expected patterns (1-3 calls for simple queries) -* No error bars in timeline +Pay attention to patterns across multiple executions. When all recent transcripts show errors, start by checking agent status, MCP server connectivity, and system prompt configuration. A spiky timeline that alternates between success and error typically points to intermittent tool failures or external API issues. If duration increases steadily over a session, your context window is likely filling up. Clear the conversation history to reset it. High token usage combined with relatively few LLM calls usually means tool results are large or your system prompts are verbose. -Warning signs: +=== Debug with Transcripts -* Red bars in timeline: Errors or failures, click to investigate -* Increasing duration: May indicate context window growth or slow tool calls -* High token usage: Check if conversation history is too long -* Many LLM calls: Agent may be stuck in loops or making unnecessary iterations -* Missing transcripts: Agent may be stopped or encountering deployment issues +Use *Transcripts* to diagnose specific issues: -Common patterns to investigate: - -* All recent transcripts show errors: Check agent status, MCP server connectivity, or system prompt -* Duration increasing over session: Context window filling up, consider clearing conversation history -* Spiky timeline (alternating success/error): Intermittent tool failures or external API issues -* High token usage with few LLM calls: Large tool results or verbose system prompts - -=== Debug with transcripts - -Use transcripts to diagnose specific issues: - -Agent not responding +If the agent is not responding: . Check the timeline for recent transcripts. If none appear, the agent may be stopped. . Verify agent status in the main *AI Agents* view. . Look for error transcripts with deployment or initialization failures. -Tool execution errors +If the agent fails during execution: . Select the failed transcript (red bar in timeline). . Expand the trace hierarchy to find the tool invocation span. . Check the span details for error messages. . Cross-reference with MCP server status. -Slow performance +If performance is slow: . Compare duration across multiple transcripts in the summary panel. . Look for specific spans with long durations (wide bars in trace list). . Check if LLM calls are taking longer than expected. . Verify tool execution time by examining nested spans. -Unexpected behavior - -. Select the transcript for the problematic request. -. Expand the full trace hierarchy to see all operations. -. Look for missing tool calls (agent didn't invoke expected tools). -. Check LLM call count: excessive calls may indicate loops. - === Track token usage and costs -View token consumption in the Summary panel when you select a transcript: - -* Input tokens: Tokens sent to the LLM (system prompt + conversation history + tool results) -* Output tokens: Tokens generated by the LLM (agent responses) -* Total tokens: Sum of input and output +View token consumption in the *Summary* panel when you select a transcript. The breakdown shows input tokens (everything sent to the LLM including system prompt, conversation history, and tool results), output tokens (what the LLM generates in agent responses), and total tokens as the sum of both. Calculate cost per request: ---- -Cost = (input_tokens × input_price) + (output_tokens × output_price) +Cost = (input_tokens x input_price) + (output_tokens x output_price) ---- Example: GPT-5.2 with 4,302 input tokens and 1,340 output tokens at $0.00000175 per input token and $0.000014 per output token costs $0.026 per request. For cost optimization strategies, see xref:ai-agents:agents/concepts.adoc#cost-calculation[Cost calculation]. -== Test agent behavior with the inspector +== Test agent behavior with Inspector The *Inspector* tab provides real-time conversation testing. Use it to test agent responses interactively and verify behavior before deploying changes. -=== Access the inspector +=== Access Inspector . Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud Console. . Click your agent name. @@ -118,13 +88,9 @@ The *Inspector* tab provides real-time conversation testing. Use it to test agen === Testing best practices -Test your agents systematically with these scenarios: +Test your agents systematically by exploring edge cases and potential failure scenarios. Begin with boundary testing. Requests at the edge of agent capabilities verify that scope enforcement works correctly. Error handling becomes clear when you request unavailable data and observe whether the agent degrades gracefully or fabricates information. -* Boundary cases: Test requests at the edge of agent capabilities to verify scope enforcement. -* Error handling: Request unavailable data to verify graceful degradation. -* Iteration count: Monitor how many iterations complex requests require. -* Ambiguous input: Send vague queries to verify clarification behavior. -* Token usage: Track tokens per request to estimate costs. +Monitor iteration counts during complex requests to ensure they complete within your configured limits. Ambiguous or vague queries reveal whether the agent asks clarifying questions or makes risky assumptions. Throughout testing, track token usage per request to estimate costs and identify which query patterns consume the most resources. == Next steps diff --git a/modules/ai-agents/pages/agents/troubleshooting.adoc b/modules/ai-agents/pages/agents/troubleshooting.adoc index e8e71ed36..eaad03967 100644 --- a/modules/ai-agents/pages/agents/troubleshooting.adoc +++ b/modules/ai-agents/pages/agents/troubleshooting.adoc @@ -175,7 +175,7 @@ Critical rules: * Include "never fabricate" rules in all system prompts * Test with requests that require unavailable data -* Monitor transcripts and session topic for fabricated responses +* Monitor *Transcripts* and session topic for fabricated responses === Analyzing conversation patterns @@ -183,7 +183,7 @@ Critical rules: **Solution:** -Review conversation history in transcripts to identify problematic patterns: +Review conversation history in *Transcripts* to identify problematic patterns: * Agents calling the same tool repeatedly: Indicates loop detection is needed * Large gaps between messages: Suggests tool timeout or slow execution @@ -193,7 +193,7 @@ Review conversation history in transcripts to identify problematic patterns: **Analysis workflow:** -. Use inspector to reproduce the issue. +. Use *Inspector* to reproduce the issue. . Review full conversation including tool invocations. . Identify where agent behavior diverged from expected. . Check system prompt for missing guidance. @@ -247,7 +247,7 @@ Diagnose and fix issues related to agent speed and resource consumption. **Solution:** -. Review token usage in transcripts. +. Review token usage in *Transcripts*. . Lower max iterations for this agent. . Optimize tool responses to return less data: + diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index 10f16a950..e4aa109cb 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -18,12 +18,7 @@ After completing this tutorial, you will be able to: Agents become powerful when they coordinate multiple tools to solve complex problems. A single-tool agent can retrieve order status. A multi-tool agent can check order status, fetch tracking information, look up customer history, and decide which tools to invoke based on conversation context. -This tutorial teaches multi-tool orchestration through a customer support scenario. You'll see how agents: - -* **Use conversation context to choose tools**: The agent analyzes what the user said and what data it already has to decide which tool to call next. -* **Chain tools in sequence**: When an order is "shipped", the agent follows up with tracking information automatically. -* **Handle missing information**: When users provide incomplete requests, the agent asks clarifying questions instead of guessing. -* **Recover from errors**: When tools return no data, the agent explains the limitation without fabricating information. +This tutorial teaches multi-tool orchestration through a customer support scenario. The patterns you practice here apply to any multi-tool scenario: data analysis agents coordinating query and visualization tools, workflow automation agents chaining approval and notification tools, or research agents combining search and summarization tools. @@ -33,9 +28,9 @@ Customer support teams handle repetitive questions: "Where is my order?", "What' An effective support agent needs three capabilities: -1. **Order status lookup**: Check current order state and contents -2. **Shipping information**: Retrieve tracking numbers and delivery estimates -3. **Order history**: Show past purchases for a customer +- **Order status lookup**: Check current order state and contents +- **Shipping information**: Retrieve tracking numbers and delivery estimates +- **Order history**: Show past purchases for a customer The challenge: users phrase requests differently ("Where's my package?", "Track order ORD-12345", "My recent orders"), and agents must choose the right tool based on context. @@ -46,15 +41,13 @@ The challenge: users phrase requests differently ("Where's my package?", "Track == Design the MCP tools -Before an agent can orchestrate tools, you need tools to orchestrate. The design principle: each tool should do one thing well, returning structured data the agent can reason about. - -=== Tool granularity matters +Before an agent can orchestrate tools, you need tools to orchestrate. Each tool should do one thing well, returning structured data the agent can reason about. -You could create a single `handle_customer_request` tool that takes a natural language query and returns an answer. This approach fails because: +You could create a single `handle_customer_request` tool that takes a natural language query and returns an answer. But, this approach fails because: * The agent can't inspect intermediate results * Tool chaining becomes impossible (no way to pass order status to shipping lookup) -* Error handling is opaque (did the order lookup fail or the shipping lookup?) +* Error handling is opaque Instead, create focused tools: @@ -66,6 +59,8 @@ This granularity enables the agent to chain tools (check order status, see it's === Deploy the tools +Create a Remote MCP server with the three tools. + . Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud Console^] . Go to *Agentic AI* > *Remote MCP* . Click *Create MCP Server* @@ -74,44 +69,37 @@ This granularity enables the agent to chain tools (check order status, see it's * *Name*: `customer-support-tools` * *Description*: `Tools for customer support agent` -. Add the first tool with this YAML configuration: +. Add the following tools. For each tool, select *Processor* from the component type dropdown, then click *Lint* to validate: ++ +[tabs] +==== +get_order_status:: ++ +This tool uses the `mapping` processor to return mock data. The mock approach enables testing without external dependencies. The agent must interpret the structured response to extract order details. + [,yaml] ---- include::ai-agents:example$mcp-tools/processors/get_order_status.yaml[] ---- -+ -This tool uses the `mapping` processor to return mock data. The mock approach enables testing without external dependencies. In production, replace `mapping` with `http` to call your actual order API. -+ -Notice the tool's design: -+ -* **Single responsibility**: Only retrieves order status, nothing else -* **Structured output**: Returns JSON the agent can parse -* **Predictable format**: Always includes `order_id`, `status`, `items`, `total` -. Select *Processor* from the component type dropdown, then click *Lint* to validate. - -. Click *Add Tool* and add the second tool: +get_shipping_info:: ++ +This tool demonstrates conditional data: it only returns tracking information when the order has shipped. When an order hasn't shipped yet, the tool returns an empty result. The agent must handle this case. + [,yaml] ---- include::ai-agents:example$mcp-tools/processors/get_shipping_info.yaml[] ---- -+ -This tool demonstrates conditional data: it only returns tracking information when the order has shipped. When an order hasn't shipped yet, the tool returns an empty result. The agent must handle this case. -+ -Select *Processor* from the component type dropdown, then click *Lint*. -. Click *Add Tool* and add the third tool: +get_customer_history:: ++ +This tool returns multiple orders, demonstrating list-handling. The agent must format multiple results clearly for users. + [,yaml] ---- include::ai-agents:example$mcp-tools/processors/get_customer_history.yaml[] ---- -+ -This tool returns multiple orders, demonstrating list-handling. The agent must format multiple results clearly for users. -+ -Select *Processor* from the component type dropdown, then click *Lint*. +==== . Click *Create MCP Server* @@ -121,83 +109,9 @@ Wait for the server status to show *Running*. You now have three focused tools t The system prompt teaches the agent how to orchestrate tools. Without explicit guidance, the agent must guess when to use each tool, often choosing incorrectly or ignoring tools entirely. -=== Specify when to use each tool - -The prompt must explicitly state when to invoke each tool. - -When the prompt doesn't specify when to use tools, the agent must guess based on tool names and descriptions alone. This leads to wrong tool choices, unnecessary calls, or skipped tools entirely. - -.Don't -[,text] ----- -You have access to order, shipping, and customer history tools. ----- - -The agent sees three tools but lacks decision criteria. When a user asks "Where's my order?", the agent might call the wrong tool first, call all tools unnecessarily, or skip tools and fabricate an answer. - -.Do -[,text] ----- -When to use tools: -- Use get_order_status when customer provides an order ID -- Use get_shipping_info when order status is "shipped" -- Use get_customer_history when customer asks about past orders ----- - -Explicit criteria create reliable tool selection. The agent follows clear rules instead of guessing. - -=== Define tool chaining logic - -Specify how tool results inform the next action. - -Without chaining instructions, agents treat each tool call as independent. They miss opportunities to combine data or make redundant calls. - -.Don't -[,text] ----- -Use get_order_status to check orders. -Use get_shipping_info for tracking information. ----- - -The agent calls `get_order_status`, receives status "shipped", but doesn't know to follow up with shipping information. Users get incomplete answers. - -.Do -[,text] ----- -If order is "shipped", follow up with get_shipping_info to provide tracking details. ----- - -The agent uses the first tool's result (whether the status is "shipped") to decide whether to invoke the second tool. This creates context-aware behavior. - -=== Set error handling constraints - -Prevent fabrication when tools fail. - -Without explicit constraints, agents invent plausible-sounding data when tools return errors or empty results. This creates hallucinations. - -.Don't -[,text] ----- -Help customers track their orders. ----- - -When `get_order_status` returns no data, the agent might invent an order status, tracking number, or delivery date that sounds real but is completely fabricated. - -.Do -[,text] ----- -Never: -- Make up tracking numbers or delivery dates -- Guess customer intent - -If order not found, ask customer to verify the order ID. ----- - -Explicit constraints force the agent to acknowledge limitations instead of fabricating information. - === Create the agent -Create the customer support agent with the designed system prompt. +Create the customer support agent with the system prompt. . Go to *Agentic AI* > *AI Agents* . Click *Create Agent* @@ -209,7 +123,7 @@ Create the customer support agent with the designed system prompt. * *Model*: OpenAI GPT-5.2 or Claude Sonnet 4.5 (models with strong reasoning) * *API Key*: Your LLM provider API key * *MCP Server*: Select `customer-support-tools` -* *Max Iterations*: 30 +* *Max Iterations*: 15 . In the *System Prompt* field, enter this configuration: + @@ -265,17 +179,15 @@ Wait for the agent status to show *Running*. == Observe orchestration in action -Testing reveals how the agent makes decisions. Watch the conversation panel in the built-in chat interface to see the agent's reasoning process unfold. +Open the *Inspector* tab in the Redpanda Cloud Console to interact with the agent. -. Go to *Agentic AI* > *AI Agents* -. Click on `customer-support-agent`. -. Open the *Inspector* tab. +Testing reveals how the agent makes decisions. Watch the conversation panel in the built-in chat interface to see the agent's reasoning process unfold. === Tool chaining based on status Test how the agent chains tools based on order status. -Enter this query in the Inspector: +Enter this query in *Inspector*: ---- Hi, I'd like to check on order ORD-12345 @@ -335,7 +247,7 @@ Enter this query: Check order ORD-99999 ---- -The tool returns no data for this order ID. Watch how the agent responds—it explains the order wasn't found and asks the customer to verify the order ID. Critically, the agent does NOT fabricate tracking numbers or order details. +The tool returns no data for this order ID. Watch how the agent responds. It explains the order wasn't found and asks the customer to verify the order ID. Critically, the agent does not fabricate tracking numbers or order details. This demonstrates error recovery without hallucination. The "Never make up tracking numbers" constraint in the system prompt prevents the agent from inventing plausible-sounding but fake information. diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index 28369b3eb..4b421c2cd 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -3,7 +3,7 @@ :page-topic-type: tutorial :personas: agent_developer, platform_admin :learning-objective-1: Design multi-agent systems with domain-specific sub-agents -:learning-objective-2: Monitor multi-agent execution using transcripts +:learning-objective-2: pass:q[Monitor multi-agent execution using *Transcripts*] :learning-objective-3: Integrate agents with streaming pipelines for event-driven processing Build a transaction dispute resolution system using multi-agent architecture, secure data handling, and execution monitoring. @@ -16,7 +16,7 @@ After completing this tutorial, you will be able to: == What you'll learn -This tutorial advances from xref:ai-agents:agents/tutorials/customer-support-agent.adoc[basic multi-tool orchestration] to multi-agent systems. You'll build a transaction dispute resolution system where a root agent delegates to specialized sub-agents (account, fraud, merchant, compliance), each with focused responsibilities and PII-protected data access. You'll also monitor execution using transcripts and process disputes from transaction streams for automated detection. +This tutorial advances from xref:ai-agents:agents/tutorials/customer-support-agent.adoc[basic multi-tool orchestration] to multi-agent systems. You'll build a transaction dispute resolution system where a root agent delegates to specialized sub-agents (account, fraud, merchant, compliance), each with focused responsibilities and PII-protected data access. You'll also monitor execution using *Transcripts* and process disputes from transaction streams for automated detection. These patterns apply beyond banking to any domain requiring specialized expertise and data security: healthcare systems, insurance claims processing, or regulatory compliance workflows. @@ -383,7 +383,7 @@ This demonstrates the escalation pattern when evidence is ambiguous and requires == Monitor multi-agent execution -The inspector shows real-time progress in the conversation panel, but transcripts provide detailed post-execution analysis with timing, token usage, and full trace hierarchy. +*Inspector* shows real-time progress in the conversation panel, but *Transcripts* provides detailed post-execution analysis with timing, token usage, and full trace hierarchy. . In the left navigation, click *Transcripts*. . Select a recent transcript from your fraud case test. @@ -671,7 +671,7 @@ The mock tools in this tutorial use hardcoded customer and transaction IDs for t * Customer IDs: `CUST-1001`, `CUST-1002`, `CUST-1003` * Transaction IDs: `TXN-89012`, `TXN-89013`, `TXN-89014`, `TXN-89015` -Use these documented test IDs when testing in the inspector or the pipeline. The sub-agents' mock tools require valid IDs to return transaction details, account history, and fraud indicators. Using other IDs (like `TXN-TEST-001` or `CUST-9999`) will cause the tools to return "not found" errors, and the root agent won't be able to complete its investigation. +Use these documented test IDs when testing in *Inspector* or the pipeline. The sub-agents' mock tools require valid IDs to return transaction details, account history, and fraud indicators. Using other IDs (like `TXN-TEST-001` or `CUST-9999`) will cause the tools to return "not found" errors, and the root agent won't be able to complete its investigation. For production deployments, replace the mock tools with API calls to your account, fraud detection, merchant verification, and compliance systems. From 783d2ee713055bb5ceddff87e754383de6920ab6 Mon Sep 17 00:00:00 2001 From: Jake Cahill <45230295+JakeSCahill@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:12:59 +0000 Subject: [PATCH 60/97] Update modules/ai-agents/examples/agents/fraud-agent-prompt.txt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- modules/ai-agents/examples/agents/fraud-agent-prompt.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/ai-agents/examples/agents/fraud-agent-prompt.txt b/modules/ai-agents/examples/agents/fraud-agent-prompt.txt index 69db69a99..3bdfeb8c7 100644 --- a/modules/ai-agents/examples/agents/fraud-agent-prompt.txt +++ b/modules/ai-agents/examples/agents/fraud-agent-prompt.txt @@ -21,12 +21,12 @@ You are the fraud detection agent for ACME Bank's dispute resolution system. You Consider these factors: -1. **Location Risk** (0-35 points) +1. **Location Risk** (0-30 points) - International vs. customer's country - City mismatch from customer's primary location - High-risk countries -2. **Merchant Risk** (0-30 points) +2. **Merchant Risk** (0-25 points) - Merchant reputation score - Fraud report history - Business verification status @@ -36,16 +36,15 @@ Consider these factors: - Unusually large for merchant category - Round numbers (potential testing) -4. **Velocity Risk** (0-15 points) +4. **Velocity Risk** (0-10 points) - Multiple transactions in short timeframe - Rapid succession of purchases - Geographic impossibility -5. **Category Risk** (0-20 points) +5. **Category Risk** (0-10 points) - Outside customer's typical categories - High-risk MCC codes - Mismatch with spending patterns - ## Risk Levels - **Critical (80-100)**: Almost certainly fraud, immediate action needed From fae5d3c87585055a8fb8b89184741293fb5d0864 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 27 Jan 2026 17:28:38 +0000 Subject: [PATCH 61/97] Apply suggestions --- .../examples/agents/fraud-agent-prompt.txt | 4 +- .../processors/calculate_fraud_score.yaml | 155 ++++++++++-------- .../check_regulatory_requirements.yaml | 14 +- .../processors/get_merchant_category.yaml | 6 +- .../mcp-tools/processors/log_audit_event.yaml | 12 ++ .../examples/pipelines/test-pipelines.sh | 8 +- 6 files changed, 114 insertions(+), 85 deletions(-) diff --git a/modules/ai-agents/examples/agents/fraud-agent-prompt.txt b/modules/ai-agents/examples/agents/fraud-agent-prompt.txt index 3bdfeb8c7..b2c8a26de 100644 --- a/modules/ai-agents/examples/agents/fraud-agent-prompt.txt +++ b/modules/ai-agents/examples/agents/fraud-agent-prompt.txt @@ -10,8 +10,8 @@ You are the fraud detection agent for ACME Bank's dispute resolution system. You ## Available Tools 1. **calculate_fraud_score**: Multi-factor fraud scoring - - Input: Transaction data, customer data, merchant reputation - - Returns: Fraud score, breakdown by factor, recommendation + - Input: transaction_id, customer_id + - Returns: Fraud score (0-100), risk level, breakdown by factor, recommendation 2. **get_risk_indicators**: Detailed fraud signal detection - Input: transaction_id diff --git a/modules/ai-agents/examples/mcp-tools/processors/calculate_fraud_score.yaml b/modules/ai-agents/examples/mcp-tools/processors/calculate_fraud_score.yaml index b0fecb828..280ddef6f 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/calculate_fraud_score.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/calculate_fraud_score.yaml @@ -1,77 +1,102 @@ label: calculate_fraud_score mapping: | - let location_risk = match { - this.transaction.location.country != this.customer.location_country => 35, - this.transaction.location.city != this.customer.primary_city => 15, - _ => 0 - } - - let merchant_risk = match { - this.merchant_reputation < 40 => 30, - this.merchant_reputation < 70 => 15, - _ => 0 - } - - let amount_risk = match { - this.transaction.amount > (this.customer.avg_transaction * 10) => 25, - this.transaction.amount > (this.customer.avg_transaction * 5) => 15, - this.transaction.amount > (this.customer.avg_transaction * 2) => 5, - _ => 0 - } - - let velocity_risk = match { - this.recent_transactions_count > 10 => 15, - this.recent_transactions_count > 5 => 8, - _ => 0 - } - - let category_risk = match { - this.transaction.merchant.category == "jewelry" && this.customer.typical_categories.contains("jewelry").not() => 20, - this.transaction.merchant.category == "electronics" && this.customer.typical_categories.contains("electronics").not() => 15, - this.transaction.merchant.category == "luxury_goods" && this.customer.typical_categories.contains("luxury_goods").not() => 15, - _ => 0 - } - - let total_score = $location_risk + $merchant_risk + $amount_risk + $velocity_risk + $category_risk - - let risk_level = match { - $total_score >= 80 => "critical", - $total_score >= 60 => "high", - $total_score >= 40 => "medium", - $total_score >= 20 => "low", - _ => "minimal" - } - - root = { - "transaction_id": this.transaction.transaction_id, - "fraud_score": $total_score, - "risk_level": $risk_level, - "score_breakdown": { - "location_risk": $location_risk, - "merchant_risk": $merchant_risk, - "amount_risk": $amount_risk, - "velocity_risk": $velocity_risk, - "category_risk": $category_risk + root = match { + this.transaction_id == "TXN-89012" && this.customer_id == "CUST-1001" => { + "transaction_id": "TXN-89012", + "customer_id": "CUST-1001", + "fraud_score": 95, + "risk_level": "critical", + "score_breakdown": { + "location_risk": 35, + "merchant_risk": 30, + "amount_risk": 25, + "velocity_risk": 0, + "category_risk": 20 + }, + "factors_detected": [ + "unusual_location", + "questionable_merchant", + "unusual_amount", + "unusual_category" + ], + "reasoning": "International transaction from Singapore with no customer history of international purchases. High-value jewelry purchase (14.5x customer average). Merchant has significant fraud indicators.", + "recommendation": "block_and_investigate" + }, + this.transaction_id == "TXN-89013" && this.customer_id == "CUST-1001" => { + "transaction_id": "TXN-89013", + "customer_id": "CUST-1001", + "fraud_score": 8, + "risk_level": "minimal", + "score_breakdown": { + "location_risk": 0, + "merchant_risk": 0, + "amount_risk": 0, + "velocity_risk": 0, + "category_risk": 0 + }, + "factors_detected": [], + "reasoning": "Local transaction from trusted merchant in customer's typical spending category and amount range.", + "recommendation": "approve" + }, + this.transaction_id == "TXN-89014" && this.customer_id == "CUST-1002" => { + "transaction_id": "TXN-89014", + "customer_id": "CUST-1002", + "fraud_score": 52, + "risk_level": "medium", + "score_breakdown": { + "location_risk": 0, + "merchant_risk": 15, + "amount_risk": 0, + "velocity_risk": 8, + "category_risk": 0 + }, + "factors_detected": [ + "questionable_merchant", + "high_velocity" + ], + "reasoning": "Recurring subscription service with known billing issues. Multiple charges detected from same merchant. Moderate merchant reputation score.", + "recommendation": "monitor_closely" + }, + this.transaction_id == "TXN-89015" && this.customer_id == "CUST-1003" => { + "transaction_id": "TXN-89015", + "customer_id": "CUST-1003", + "fraud_score": 12, + "risk_level": "minimal", + "score_breakdown": { + "location_risk": 0, + "merchant_risk": 0, + "amount_risk": 5, + "velocity_risk": 0, + "category_risk": 0 + }, + "factors_detected": [ + "slightly_elevated_amount" + ], + "reasoning": "International hotel charge consistent with customer's frequent travel patterns. Amount within expected range for lodging category.", + "recommendation": "approve" }, - "factors_detected": [ - if $location_risk > 0 { "unusual_location" }, - if $merchant_risk > 0 { "questionable_merchant" }, - if $amount_risk > 0 { "unusual_amount" }, - if $velocity_risk > 0 { "high_velocity" }, - if $category_risk > 0 { "unusual_category" } - ].filter(f -> f != null), - "recommendation": match { - $total_score >= 80 => "block_and_investigate", - $total_score >= 60 => "hold_for_review", - $total_score >= 40 => "monitor_closely", - _ => "approve" + _ => { + "transaction_id": this.transaction_id, + "customer_id": this.customer_id, + "fraud_score": 50, + "risk_level": "medium", + "score_breakdown": { + "location_risk": 0, + "merchant_risk": 0, + "amount_risk": 0, + "velocity_risk": 0, + "category_risk": 0 + }, + "factors_detected": [], + "reasoning": "Insufficient data to calculate accurate fraud score for this transaction/customer combination.", + "recommendation": "monitor_closely" } } meta: mcp: enabled: true - description: "Calculate fraud risk score based on transaction patterns and risk indicators. Returns risk level and recommendation." + description: "Calculate fraud risk score based on transaction patterns and risk indicators. Use TXN-89012 through TXN-89015 with corresponding customer IDs for testing." properties: - name: transaction_id type: string diff --git a/modules/ai-agents/examples/mcp-tools/processors/check_regulatory_requirements.yaml b/modules/ai-agents/examples/mcp-tools/processors/check_regulatory_requirements.yaml index a3a6e189e..f8df06efd 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/check_regulatory_requirements.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/check_regulatory_requirements.yaml @@ -108,17 +108,9 @@ mapping: | meta: mcp: enabled: true - description: "Check regulatory requirements for dispute resolution based on transaction type, amount, and jurisdiction." + description: "Check regulatory requirements for dispute resolution based on dispute type." properties: - - name: transaction_type + - name: dispute_type type: string - description: "Type of transaction (card_not_present, international, recurring, travel)" - required: true - - name: amount - type: number - description: "Transaction amount in USD" - required: true - - name: jurisdiction - type: string - description: "Geographic jurisdiction (USA, EU, APAC)" + description: "Type of dispute (fraud, billing_error, service_not_received)" required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_merchant_category.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_merchant_category.yaml index b8dc484da..f8ac390f1 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/get_merchant_category.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/get_merchant_category.yaml @@ -82,9 +82,9 @@ mapping: | meta: mcp: enabled: true - description: "Retrieve merchant category information including MCC code, fraud risk level, and common patterns. Use LUXURY WATCHES INT, EXAMPLE STREAMING, or HOTEL PARIS for testing." + description: "Retrieve merchant category information including fraud risk level and common patterns based on MCC code." properties: - - name: merchant_name + - name: mcc type: string - description: "Merchant name as it appears on transaction" + description: "Merchant Category Code (5944 for jewelry, 5942 for books, 4899 for streaming, 7011 for hotels)" required: true diff --git a/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml b/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml index ade8773ff..1344e34d0 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml @@ -33,6 +33,18 @@ meta: type: string description: "Dispute resolution decision (approve_refund, deny_claim, etc.)" required: true + - name: risk_score + type: number + description: "Calculated fraud risk score (0-100)" + required: true + - name: evidence + type: object + description: "Evidence reviewed during investigation" + required: true + - name: outcome + type: string + description: "Final outcome of the dispute (approved, denied, escalated, pending)" + required: true - name: escalated type: boolean description: "Whether case was escalated for manual review" diff --git a/modules/ai-agents/examples/pipelines/test-pipelines.sh b/modules/ai-agents/examples/pipelines/test-pipelines.sh index 7945649fb..b8bfd49d5 100755 --- a/modules/ai-agents/examples/pipelines/test-pipelines.sh +++ b/modules/ai-agents/examples/pipelines/test-pipelines.sh @@ -78,10 +78,10 @@ for file in *.yaml; do echo "$output" | sed 's/^/ /' FAILED=$((FAILED + 1)) else - # Other lint error - echo -e "${YELLOW}WARNING${NC}" - echo "$output" | sed 's/^/ /' | head -5 - CLOUD_PROCESSOR_ERRORS=$((CLOUD_PROCESSOR_ERRORS + 1)) + # Other lint error (unexpected) + echo -e "${RED}FAILED${NC}" + echo "$output" | sed 's/^/ /' + FAILED=$((FAILED + 1)) fi fi done From 5c20723abccc3e01157db637a7f6248724510dac Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 27 Jan 2026 17:36:53 +0000 Subject: [PATCH 62/97] Apply suggestions --- .../mcp-tools/processors/log_audit_event.yaml | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml b/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml index 1344e34d0..57b0a81a5 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/log_audit_event.yaml @@ -1,20 +1,25 @@ label: log_audit_event -mapping: | - root = { - "audit_id": uuid_v4(), - "timestamp": now(), - "event_type": "dispute_investigation", - "transaction_id": this.transaction_id, - "customer_id": this.customer_id, - "agent_decision": this.decision, - "risk_score": this.risk_score, - "evidence_reviewed": this.evidence, - "outcome": this.outcome, - "escalated": this.escalated, - "compliance_notes": this.notes, - "logged_by": "dispute-resolution-agent", - "status": "recorded" - } +processors: + - mapping: | + root = { + "audit_id": uuid_v4(), + "timestamp": now(), + "event_type": "dispute_investigation", + "transaction_id": this.transaction_id, + "customer_id": this.customer_id, + "agent_decision": this.decision, + "risk_score": this.risk_score, + "evidence_reviewed": this.evidence, + "outcome": this.outcome, + "escalated": this.escalated, + "compliance_notes": this.notes, + "logged_by": "dispute-resolution-agent", + "status": "recorded" + } + + - log: + level: INFO + message: "Compliance audit event: ${!json()}" meta: mcp: From 33ecc8b8a23e9384b2a60aef3250fd0c8e185926 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Tue, 27 Jan 2026 11:53:54 -0700 Subject: [PATCH 63/97] update cloud overview adp focus --- modules/get-started/pages/cloud-overview.adoc | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/modules/get-started/pages/cloud-overview.adoc b/modules/get-started/pages/cloud-overview.adoc index 9e03a35b8..ff215f42b 100644 --- a/modules/get-started/pages/cloud-overview.adoc +++ b/modules/get-started/pages/cloud-overview.adoc @@ -1,14 +1,57 @@ = Redpanda Cloud Overview -:description: Learn about Redpanda Serverless, Bring Your Own Cloud (BYOC), and Dedicated clusters. +:description: Learn about the Redpanda Agent Development Platform (ADP) and deployment options including BYOC, Dedicated, and Serverless clusters. :page-aliases: cloud:dedicated-byoc.adoc, deploy:deployment-option/cloud/dedicated-byoc.adoc, deploy:deployment-option/cloud/cloud-overview.adoc Redpanda Cloud is a complete data streaming platform delivered as a fully-managed service. It provides automated upgrades and patching, data balancing, and support while continuously monitoring your clusters and underlying infrastructure to meet strict performance, availability, reliability, and security requirements. All Redpanda Cloud clusters are deployed with an integrated glossterm:Redpanda Console[], and all clusters have access to unlimited retention and 300+ data connectors with xref:develop:connect/about.adoc[Redpanda Connect]. TIP: For more detailed information about the Redpanda platform, see xref:get-started:intro-to-events.adoc[] and xref:get-started:architecture.adoc[]. +== Redpanda Agent Development Platform (ADP) + +Redpanda Cloud delivers the Agent Development Platform (ADP), an enterprise-grade infrastructure for building, deploying, and managing AI agents at scale. ADP provides unified governance, observability, and security for agentic applications while leveraging Redpanda's streaming and analytical capabilities as the foundational data fabric. + +=== What is ADP? + +ADP combines multiple components into a cohesive platform: + +* **AI Agents**: Deploy declarative agents or bring your own agent frameworks (LangChain, LlamaIndex, and others) +* **MCP Servers**: Expose data and actions to agents through the Model Context Protocol, built on xref:develop:connect/about.adoc[Redpanda Connect] +* **AI Gateway**: Manage LLM provider access with cost controls, rate limiting, and intelligent routing +* **Catalog**: Centralized view of all agents, tools, and access policies across your organization + +=== Enterprise capabilities + +ADP addresses critical enterprise requirements for AI agent deployments: + +**Unified authorization**:: All components use OIDC-based authentication with an "on-behalf-of" authorization model. When a user invokes an agent, the agent inherits the intersection of its own permissions and the user's permissions, ensuring proper data access scoping. + +**Complete observability**:: ADP captures execution logs (agent transcripts) using OpenTelemetry standards with 100% trace sampling. View agent actions in Redpanda Console, query execution history with OXLA, and replay data for agent evaluations. + +**Compliance and audit**:: For industries requiring multi-year audit trails, ADP records every agent action and data source used in decision-making. Execution logs are stored in Redpanda topics and can be materialized to Iceberg tables for long-term retention and analysis. + +=== ADP deployment model + +ADP is primarily deployed using the <> model, where: + +* The **control plane** (managed by Redpanda) handles provisioning, monitoring, and policy management +* The **data plane** (deployed in your cloud account) runs the ADP components, Redpanda cluster, and OXLA query engine +* All your data remains in your infrastructure, providing maximum security and data sovereignty + +ADP installations include both Redpanda (for streaming and event storage) and OXLA (for SQL-based queries and analytics), forming the data fabric that powers agent applications. + +=== Use cases + +Organizations use ADP to: + +* **Extend Microsoft Copilot**: Integrate Office 365 agents with internal data sources that Copilot cannot access +* **Build domain-specific agents**: Create specialized agents for operational tasks, like querying building management systems or financial compliance workflows +* **Enable non-technical users**: Allow business users to interact with complex data through natural language interfaces instead of dashboards or SQL + +For details about building agents and MCP servers, see xref:ai-agents:index.adoc[]. + == Redpanda Cloud cluster types -Redpanda Cloud offers three fully-managed cloud deployment options, each designed for different use cases: +ADP and Redpanda Cloud applications are supported by three fully-managed deployment options. For ADP deployments, <> is the primary model, providing the security and data sovereignty required for enterprise agent platforms. For streaming applications, all three options are available: * **<>**: Fastest way to get started with automatic scaling * **<>**: Production clusters in Redpanda's cloud with enhanced isolation @@ -28,6 +71,11 @@ Redpanda Cloud offers three fully-managed cloud deployment options, each designe | Production clusters requiring cloud hosting, higher throughput, and extra isolation | Production clusters requiring data sovereignty, the highest throughput, and added security +| *ADP support* +| Development and testing +| Development and testing +| Production (primary deployment model) + | *Deployment* | Redpanda's cloud (AWS/GCP) | Redpanda's cloud (AWS/Azure/GCP) @@ -153,6 +201,8 @@ Redpanda creates a cloud organization for you and sends you a welcome email. === Bring Your Own Cloud (BYOC) +BYOC is the primary deployment model for the Agent Development Platform (ADP). With BYOC clusters, the Redpanda data plane (including ADP components, Redpanda brokers, and OXLA) deploys into your existing VPC or VNet, ensuring all data remains in your environment. + With BYOC clusters, you deploy the Redpanda glossterm:data plane[] into your existing VPC (for AWS and GCP) or VNet (for Azure), and all data is contained in your own environment. This provides an additional layer of security and isolation. (See xref:get-started:byoc-arch.adoc[].) Redpanda manages provisioning, monitoring, upgrades, and security policies, and it manages required resources in your VPC or VNet, including subnets (subnetworks in GCP), IAM roles, and object storage resources (for example, S3 buckets or Azure Storage accounts). @@ -395,10 +445,11 @@ The following features are currently in beta in Redpanda Cloud: * BYOVPC for AWS * BYOVNet for Azure * Secrets Management for BYOVPC on GCP and AWS -* xref:ai-agents:index.adoc[AI agents with MCP servers] +* xref:ai-agents:index.adoc[Agent Development Platform (ADP)] including AI agents, MCP servers, AI Gateway, and Catalog * Several Redpanda Connect components == Next steps +* xref:ai-agents:index.adoc[Build AI agents with ADP] * xref:manage:maintenance.adoc[Learn about upgrades and maintenance] * xref:get-started:cluster-types/serverless.adoc[Create a Serverless cluster] * xref:get-started:cluster-types/byoc/index.adoc[Create a BYOC cluster] From 89b9fa70ae098ca83ec12e42343036e1a13e2cd5 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Tue, 27 Jan 2026 13:12:32 -0700 Subject: [PATCH 64/97] Update cloud overview with latest ADP content from CTO Tech Talk demo Align messaging with public demo content: emphasize manufacturing use case, add multi-agent architecture, highlight MCP server security features (injection attack prevention), strengthen interoperability messaging, and remove OXLA references not yet in public materials. Co-Authored-By: Claude Sonnet 4.5 --- modules/get-started/pages/cloud-overview.adoc | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/modules/get-started/pages/cloud-overview.adoc b/modules/get-started/pages/cloud-overview.adoc index ff215f42b..51e1a22e9 100644 --- a/modules/get-started/pages/cloud-overview.adoc +++ b/modules/get-started/pages/cloud-overview.adoc @@ -12,20 +12,22 @@ Redpanda Cloud delivers the Agent Development Platform (ADP), an enterprise-grad === What is ADP? -ADP combines multiple components into a cohesive platform: +ADP combines multiple components into a cohesive platform built on open standards for maximum interoperability: -* **AI Agents**: Deploy declarative agents or bring your own agent frameworks (LangChain, LlamaIndex, and others) -* **MCP Servers**: Expose data and actions to agents through the Model Context Protocol, built on xref:develop:connect/about.adoc[Redpanda Connect] -* **AI Gateway**: Manage LLM provider access with cost controls, rate limiting, and intelligent routing +* **AI Agents**: Deploy declarative agents or bring your own agent frameworks (LangChain, LlamaIndex, and others). Build multi-agent systems where specialized sub-agents handle specific responsibilities, following single-responsibility principles. +* **MCP Servers**: Build lightweight data and action interfaces using a low-code framework based on xref:develop:connect/about.adoc[Redpanda Connect]. Connect to hundreds of data sources (databases, cloud storage, APIs) and enforce fine-grained policies that programmatically prevent prompt injection and SQL injection attacks. MCP servers are extremely lightweight—run dozens on minimal resources—with OIDC-based access control and real-time debugging capabilities. +* **AI Gateway**: Manage LLM provider access with cost controls, rate limiting, intelligent routing, and failover support across multiple providers * **Catalog**: Centralized view of all agents, tools, and access policies across your organization === Enterprise capabilities ADP addresses critical enterprise requirements for AI agent deployments: +**Security by design**:: MCP servers enforce policies at the tool level, programmatically preventing prompt injection, SQL injection, and other agent-based attacks. Policy enforcement is deterministic and controlled—agents cannot bypass security constraints even through creative prompting. + **Unified authorization**:: All components use OIDC-based authentication with an "on-behalf-of" authorization model. When a user invokes an agent, the agent inherits the intersection of its own permissions and the user's permissions, ensuring proper data access scoping. -**Complete observability**:: ADP captures execution logs (agent transcripts) using OpenTelemetry standards with 100% trace sampling. View agent actions in Redpanda Console, query execution history with OXLA, and replay data for agent evaluations. +**Complete observability**:: ADP provides two levels of inspection: execution logs (transcripts) capture every agent action with 100% sampling using OpenTelemetry standards, while real-time debugging tools allow inspection of individual MCP server calls. Traces span across services and go as deep as needed—down to individual tool invocations with full timing data. View detailed agent actions in Redpanda Console and replay data for agent evaluations. **Compliance and audit**:: For industries requiring multi-year audit trails, ADP records every agent action and data source used in decision-making. Execution logs are stored in Redpanda topics and can be materialized to Iceberg tables for long-term retention and analysis. @@ -34,18 +36,18 @@ ADP addresses critical enterprise requirements for AI agent deployments: ADP is primarily deployed using the <> model, where: * The **control plane** (managed by Redpanda) handles provisioning, monitoring, and policy management -* The **data plane** (deployed in your cloud account) runs the ADP components, Redpanda cluster, and OXLA query engine +* The **data plane** (deployed in your cloud account) runs the ADP components and Redpanda cluster * All your data remains in your infrastructure, providing maximum security and data sovereignty -ADP installations include both Redpanda (for streaming and event storage) and OXLA (for SQL-based queries and analytics), forming the data fabric that powers agent applications. +ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code—all components use standardized protocols for seamless interoperability. === Use cases Organizations use ADP to: -* **Extend Microsoft Copilot**: Integrate Office 365 agents with internal data sources that Copilot cannot access -* **Build domain-specific agents**: Create specialized agents for operational tasks, like querying building management systems or financial compliance workflows -* **Enable non-technical users**: Allow business users to interact with complex data through natural language interfaces instead of dashboards or SQL +* **Monitor manufacturing and operations**: Deploy multi-agent systems that analyze factory machine telemetry in real-time, detect anomalies, search equipment manuals, and create maintenance tickets automatically. Use data aggregation patterns (like tumbling windows) to process high-volume sensor data before sending insights to agents. +* **Extend enterprise productivity tools**: Integrate Microsoft Copilot or other workplace agents with internal data sources and systems that are otherwise inaccessible +* **Automate operational workflows**: Create specialized agents for building management, infrastructure monitoring, compliance reporting, and other domain-specific tasks For details about building agents and MCP servers, see xref:ai-agents:index.adoc[]. @@ -201,7 +203,7 @@ Redpanda creates a cloud organization for you and sends you a welcome email. === Bring Your Own Cloud (BYOC) -BYOC is the primary deployment model for the Agent Development Platform (ADP). With BYOC clusters, the Redpanda data plane (including ADP components, Redpanda brokers, and OXLA) deploys into your existing VPC or VNet, ensuring all data remains in your environment. +BYOC is the primary deployment model for the Agent Development Platform (ADP). With BYOC clusters, the Redpanda data plane (including ADP components and Redpanda brokers) deploys into your existing VPC or VNet, ensuring all data remains in your environment. With BYOC clusters, you deploy the Redpanda glossterm:data plane[] into your existing VPC (for AWS and GCP) or VNet (for Azure), and all data is contained in your own environment. This provides an additional layer of security and isolation. (See xref:get-started:byoc-arch.adoc[].) Redpanda manages provisioning, monitoring, upgrades, and security policies, and it manages required resources in your VPC or VNet, including subnets (subnetworks in GCP), IAM roles, and object storage resources (for example, S3 buckets or Azure Storage accounts). From 8668d63f46deb44cc4ae91b4c43afb9396f44f3e Mon Sep 17 00:00:00 2001 From: micheleRP Date: Tue, 27 Jan 2026 15:25:44 -0700 Subject: [PATCH 65/97] cleanup --- modules/get-started/pages/cloud-overview.adoc | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/modules/get-started/pages/cloud-overview.adoc b/modules/get-started/pages/cloud-overview.adoc index 51e1a22e9..59aa00eb0 100644 --- a/modules/get-started/pages/cloud-overview.adoc +++ b/modules/get-started/pages/cloud-overview.adoc @@ -1,59 +1,57 @@ = Redpanda Cloud Overview -:description: Learn about the Redpanda Agent Development Platform (ADP) and deployment options including BYOC, Dedicated, and Serverless clusters. +:description: Learn about the Redpanda Agentic Data Plane (ADP) and deployment options including BYOC, Dedicated, and Serverless clusters. :page-aliases: cloud:dedicated-byoc.adoc, deploy:deployment-option/cloud/dedicated-byoc.adoc, deploy:deployment-option/cloud/cloud-overview.adoc -Redpanda Cloud is a complete data streaming platform delivered as a fully-managed service. It provides automated upgrades and patching, data balancing, and support while continuously monitoring your clusters and underlying infrastructure to meet strict performance, availability, reliability, and security requirements. All Redpanda Cloud clusters are deployed with an integrated glossterm:Redpanda Console[], and all clusters have access to unlimited retention and 300+ data connectors with xref:develop:connect/about.adoc[Redpanda Connect]. +Redpanda Cloud is a complete data streaming and agentic data plane platform delivered as a fully-managed service. It provides automated upgrades and patching, data balancing, and support while continuously monitoring your clusters and underlying infrastructure to meet strict performance, availability, reliability, and security requirements. All Redpanda Cloud clusters are deployed with an integrated glossterm:Redpanda Console[], and all clusters have access to unlimited retention and 300+ data connectors with xref:develop:connect/about.adoc[Redpanda Connect]. -TIP: For more detailed information about the Redpanda platform, see xref:get-started:intro-to-events.adoc[] and xref:get-started:architecture.adoc[]. +== Redpanda Agentic Data Plane (ADP) -== Redpanda Agent Development Platform (ADP) +Redpanda ADP is an enterprise-grade infrastructure for building, deploying, and managing AI agents at scale. Redpanda ADP provides unified governance, observability, and security for agentic applications while leveraging Redpanda's streaming and analytical capabilities as the foundational data fabric. -Redpanda Cloud delivers the Agent Development Platform (ADP), an enterprise-grade infrastructure for building, deploying, and managing AI agents at scale. ADP provides unified governance, observability, and security for agentic applications while leveraging Redpanda's streaming and analytical capabilities as the foundational data fabric. +=== What is Redpanda ADP? -=== What is ADP? +Redpanda ADP combines multiple components into a cohesive platform built on open standards for maximum interoperability: -ADP combines multiple components into a cohesive platform built on open standards for maximum interoperability: - -* **AI Agents**: Deploy declarative agents or bring your own agent frameworks (LangChain, LlamaIndex, and others). Build multi-agent systems where specialized sub-agents handle specific responsibilities, following single-responsibility principles. -* **MCP Servers**: Build lightweight data and action interfaces using a low-code framework based on xref:develop:connect/about.adoc[Redpanda Connect]. Connect to hundreds of data sources (databases, cloud storage, APIs) and enforce fine-grained policies that programmatically prevent prompt injection and SQL injection attacks. MCP servers are extremely lightweight—run dozens on minimal resources—with OIDC-based access control and real-time debugging capabilities. -* **AI Gateway**: Manage LLM provider access with cost controls, rate limiting, intelligent routing, and failover support across multiple providers -* **Catalog**: Centralized view of all agents, tools, and access policies across your organization +* **AI agents**: Deploy declarative agents or bring your own agent frameworks (LangChain, LlamaIndex, and others). Build multi-agent systems where specialized sub-agents handle specific responsibilities, following single-responsibility principles. +* **MCP servers**: Build lightweight data and action interfaces using a low-code framework based on xref:develop:connect/about.adoc[Redpanda Connect]. Connect to hundreds of data sources (databases, cloud storage, APIs) and enforce fine-grained policies that programmatically prevent prompt injection and SQL injection attacks. MCP servers are extremely lightweight—run dozens on minimal resources—with OIDC-based access control and real-time debugging capabilities. +* **AI Gateway**: Manage LLM provider access with cost controls, rate limiting, intelligent routing, and failover support across multiple providers. +//* **Catalog**: Maintain a centralized repository of agents, MCP servers, tools, and policies. Share components across teams and enforce organization-wide standards. === Enterprise capabilities -ADP addresses critical enterprise requirements for AI agent deployments: +Redpanda ADP addresses critical enterprise requirements for AI agent deployments: -**Security by design**:: MCP servers enforce policies at the tool level, programmatically preventing prompt injection, SQL injection, and other agent-based attacks. Policy enforcement is deterministic and controlled—agents cannot bypass security constraints even through creative prompting. +*Security by design*: MCP servers enforce policies at the tool level, programmatically preventing prompt injection, SQL injection, and other agent-based attacks. Policy enforcement is deterministic and controlled—agents cannot bypass security constraints even through creative prompting. -**Unified authorization**:: All components use OIDC-based authentication with an "on-behalf-of" authorization model. When a user invokes an agent, the agent inherits the intersection of its own permissions and the user's permissions, ensuring proper data access scoping. +*Unified authorization*: All components use OIDC-based authentication with an "on-behalf-of" authorization model. When a user invokes an agent, the agent inherits the intersection of its own permissions and the user's permissions, ensuring proper data access scoping. -**Complete observability**:: ADP provides two levels of inspection: execution logs (transcripts) capture every agent action with 100% sampling using OpenTelemetry standards, while real-time debugging tools allow inspection of individual MCP server calls. Traces span across services and go as deep as needed—down to individual tool invocations with full timing data. View detailed agent actions in Redpanda Console and replay data for agent evaluations. +*Complete observability*: Redpanda ADP provides two levels of inspection: execution logs (transcripts) capture every agent action with 100% sampling using OpenTelemetry standards, while real-time debugging tools allow inspection of individual MCP server calls. Traces span across services and go as deep as needed—down to individual tool invocations with full timing data. View detailed agent actions in Redpanda Console and replay data for agent evaluations. -**Compliance and audit**:: For industries requiring multi-year audit trails, ADP records every agent action and data source used in decision-making. Execution logs are stored in Redpanda topics and can be materialized to Iceberg tables for long-term retention and analysis. +*Compliance and audit*: For industries requiring multi-year audit trails, Redpanda ADP records every agent action and data source used in decision-making. Execution logs are stored in Redpanda topics and can be materialized to Iceberg tables for long-term retention and analysis. -=== ADP deployment model +=== Redpanda ADP deployment model -ADP is primarily deployed using the <> model, where: +Redpanda ADP is deployed using the <> model, where: * The **control plane** (managed by Redpanda) handles provisioning, monitoring, and policy management -* The **data plane** (deployed in your cloud account) runs the ADP components and Redpanda cluster +* The **data plane** (deployed in your cloud account) runs the Redpanda ADP components and cluster * All your data remains in your infrastructure, providing maximum security and data sovereignty -ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code—all components use standardized protocols for seamless interoperability. +Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. All components use standardized protocols for seamless interoperability. === Use cases -Organizations use ADP to: +Organizations can use Redpanda ADP to: -* **Monitor manufacturing and operations**: Deploy multi-agent systems that analyze factory machine telemetry in real-time, detect anomalies, search equipment manuals, and create maintenance tickets automatically. Use data aggregation patterns (like tumbling windows) to process high-volume sensor data before sending insights to agents. -* **Extend enterprise productivity tools**: Integrate Microsoft Copilot or other workplace agents with internal data sources and systems that are otherwise inaccessible -* **Automate operational workflows**: Create specialized agents for building management, infrastructure monitoring, compliance reporting, and other domain-specific tasks +* *Automate operational workflows*: Create specialized agents for building management, infrastructure monitoring, compliance reporting, and other domain-specific tasks +* *Monitor manufacturing and operations*: Deploy multi-agent systems that analyze factory machine telemetry in real-time, detect anomalies, search equipment manuals, and create maintenance tickets automatically. Use data aggregation patterns (like tumbling windows) to process high-volume sensor data before sending insights to agents. +* *Extend enterprise productivity tools*: Integrate Microsoft Copilot or other workplace agents with internal data sources and systems that are otherwise inaccessible For details about building agents and MCP servers, see xref:ai-agents:index.adoc[]. -== Redpanda Cloud cluster types +== Redpanda Cloud deployment options -ADP and Redpanda Cloud applications are supported by three fully-managed deployment options. For ADP deployments, <> is the primary model, providing the security and data sovereignty required for enterprise agent platforms. For streaming applications, all three options are available: +Redpanda Cloud applications are supported by three fully-managed deployment options: * **<>**: Fastest way to get started with automatic scaling * **<>**: Production clusters in Redpanda's cloud with enhanced isolation @@ -73,16 +71,16 @@ ADP and Redpanda Cloud applications are supported by three fully-managed deploym | Production clusters requiring cloud hosting, higher throughput, and extra isolation | Production clusters requiring data sovereignty, the highest throughput, and added security -| *ADP support* -| Development and testing -| Development and testing -| Production (primary deployment model) - | *Deployment* | Redpanda's cloud (AWS/GCP) | Redpanda's cloud (AWS/Azure/GCP) | Your cloud account (AWS/Azure/GCP) +| *Redpanda ADP* +| ✗ +| ✗ +| ✓ + | *Tenancy* | Multi-tenant | Single-tenant @@ -109,7 +107,7 @@ ADP and Redpanda Cloud applications are supported by three fully-managed deploym | 20 (default), 32 (max) | *Private networking* -| ✗ +| ✓ | ✓ | ✓ @@ -203,7 +201,7 @@ Redpanda creates a cloud organization for you and sends you a welcome email. === Bring Your Own Cloud (BYOC) -BYOC is the primary deployment model for the Agent Development Platform (ADP). With BYOC clusters, the Redpanda data plane (including ADP components and Redpanda brokers) deploys into your existing VPC or VNet, ensuring all data remains in your environment. +With BYOC clusters, the Redpanda data plane (including Redpanda ADP components and Redpanda brokers) deploys into your existing VPC or VNet, ensuring all data remains in your environment. With BYOC clusters, you deploy the Redpanda glossterm:data plane[] into your existing VPC (for AWS and GCP) or VNet (for Azure), and all data is contained in your own environment. This provides an additional layer of security and isolation. (See xref:get-started:byoc-arch.adoc[].) Redpanda manages provisioning, monitoring, upgrades, and security policies, and it manages required resources in your VPC or VNet, including subnets (subnetworks in GCP), IAM roles, and object storage resources (for example, S3 buckets or Azure Storage accounts). @@ -214,6 +212,8 @@ With BYOVPC/BYOVNet clusters, you take full control of the networking lifecycle. The BYOC infrastructure that Redpanda manages should not be used to deploy any other workloads. +For details about the control plane - data plane framework in BYOC, see xref:get-started:byoc-arch.adoc[BYOC architecture]. + ==== Sign up for BYOC To start using BYOC, contact https://redpanda.com/try-redpanda?section=enterprise-trial[Redpanda sales^] to request a private offer with possible discounts. You are billed directly or through Google Cloud Marketplace or AWS Marketplace. @@ -229,7 +229,6 @@ Serverless clusters are a good fit for the following use cases: Consider BYOC or Dedicated if you need more control over the deployment or if you have workloads with consistently-high throughput. BYOC and Dedicated clusters offer the following features: -* Private networking * Multiple availability zones (AZs). A multi-AZ cluster provides higher resiliency in the event of a failure in one of the zones. * Role-based access control (RBAC) in the data plane * Kafka Connect @@ -239,7 +238,11 @@ Consider BYOC or Dedicated if you need more control over the deployment or if yo When you sign up for a Redpanda account, Redpanda creates an organization for you. Your organization contains all your Redpanda resources, including your clusters and networks. Within your organization, Redpanda creates a default resource group to contain your resources. You can rename this resource group, and you can create more resource groups. For example, you may want different resource groups for production and testing. -For details about the control plane - data plane framework in BYOC, see xref:get-started:byoc-arch.adoc[BYOC architecture]. +[TIP] +==== + +For more detailed information about the Redpanda platform, see xref:get-started:intro-to-events.adoc[] and xref:get-started:architecture.adoc[]. +==== == Shared responsibility model @@ -434,7 +437,6 @@ Features in limited availability are production-ready and are covered by Redpand The following features are currently in limited availability in Redpanda Cloud: -* Serverless * Dedicated for Azure == Features in beta @@ -443,15 +445,14 @@ Features in beta are available for testing and feedback. They are not covered by The following features are currently in beta in Redpanda Cloud: -* Serverless on GCP * BYOVPC for AWS * BYOVNet for Azure * Secrets Management for BYOVPC on GCP and AWS -* xref:ai-agents:index.adoc[Agent Development Platform (ADP)] including AI agents, MCP servers, AI Gateway, and Catalog +* xref:ai-agents:index.adoc[Redpanda ADP] including AI agents, MCP servers, AI Gateway, and Transcripts * Several Redpanda Connect components == Next steps -* xref:ai-agents:index.adoc[Build AI agents with ADP] +* xref:ai-agents:index.adoc[Build AI agents with Redpanda ADP] * xref:manage:maintenance.adoc[Learn about upgrades and maintenance] * xref:get-started:cluster-types/serverless.adoc[Create a Serverless cluster] * xref:get-started:cluster-types/byoc/index.adoc[Create a BYOC cluster] From cce5fb2487dd2faa90be33c18e57fcc439c90347 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Tue, 27 Jan 2026 23:18:57 -0700 Subject: [PATCH 66/97] minor edits --- modules/get-started/pages/cloud-overview.adoc | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/modules/get-started/pages/cloud-overview.adoc b/modules/get-started/pages/cloud-overview.adoc index 59aa00eb0..9e2a4d2a9 100644 --- a/modules/get-started/pages/cloud-overview.adoc +++ b/modules/get-started/pages/cloud-overview.adoc @@ -8,6 +8,10 @@ Redpanda Cloud is a complete data streaming and agentic data plane platform deli Redpanda ADP is an enterprise-grade infrastructure for building, deploying, and managing AI agents at scale. Redpanda ADP provides unified governance, observability, and security for agentic applications while leveraging Redpanda's streaming and analytical capabilities as the foundational data fabric. +Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. All components use standardized protocols for seamless interoperability. + +NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. + === What is Redpanda ADP? Redpanda ADP combines multiple components into a cohesive platform built on open standards for maximum interoperability: @@ -17,28 +21,18 @@ Redpanda ADP combines multiple components into a cohesive platform built on open * **AI Gateway**: Manage LLM provider access with cost controls, rate limiting, intelligent routing, and failover support across multiple providers. //* **Catalog**: Maintain a centralized repository of agents, MCP servers, tools, and policies. Share components across teams and enforce organization-wide standards. -=== Enterprise capabilities +=== Enterprise capabilities for AI agents Redpanda ADP addresses critical enterprise requirements for AI agent deployments: -*Security by design*: MCP servers enforce policies at the tool level, programmatically preventing prompt injection, SQL injection, and other agent-based attacks. Policy enforcement is deterministic and controlled—agents cannot bypass security constraints even through creative prompting. +*Security by design*: MCP servers enforce policies at the tool level, programmatically preventing prompt injection, SQL injection, and other agent-based attacks. Policy enforcement is deterministic and controlled. Agents cannot bypass security constraints even through creative prompting. *Unified authorization*: All components use OIDC-based authentication with an "on-behalf-of" authorization model. When a user invokes an agent, the agent inherits the intersection of its own permissions and the user's permissions, ensuring proper data access scoping. -*Complete observability*: Redpanda ADP provides two levels of inspection: execution logs (transcripts) capture every agent action with 100% sampling using OpenTelemetry standards, while real-time debugging tools allow inspection of individual MCP server calls. Traces span across services and go as deep as needed—down to individual tool invocations with full timing data. View detailed agent actions in Redpanda Console and replay data for agent evaluations. +*Complete observability*: Redpanda ADP provides two levels of inspection: execution logs (transcripts) capture every agent action with 100% sampling using OpenTelemetry standards, while real-time debugging tools allow inspection of individual MCP server calls. Traces span across services and go as deep as needed, down to individual tool invocations with full timing data. View detailed agent actions in Redpanda Console and replay data for agent evaluations. *Compliance and audit*: For industries requiring multi-year audit trails, Redpanda ADP records every agent action and data source used in decision-making. Execution logs are stored in Redpanda topics and can be materialized to Iceberg tables for long-term retention and analysis. -=== Redpanda ADP deployment model - -Redpanda ADP is deployed using the <> model, where: - -* The **control plane** (managed by Redpanda) handles provisioning, monitoring, and policy management -* The **data plane** (deployed in your cloud account) runs the Redpanda ADP components and cluster -* All your data remains in your infrastructure, providing maximum security and data sovereignty - -Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. All components use standardized protocols for seamless interoperability. - === Use cases Organizations can use Redpanda ADP to: @@ -47,7 +41,7 @@ Organizations can use Redpanda ADP to: * *Monitor manufacturing and operations*: Deploy multi-agent systems that analyze factory machine telemetry in real-time, detect anomalies, search equipment manuals, and create maintenance tickets automatically. Use data aggregation patterns (like tumbling windows) to process high-volume sensor data before sending insights to agents. * *Extend enterprise productivity tools*: Integrate Microsoft Copilot or other workplace agents with internal data sources and systems that are otherwise inaccessible -For details about building agents and MCP servers, see xref:ai-agents:index.adoc[]. +See also: xref:ai-agents:index.adoc[]. == Redpanda Cloud deployment options @@ -176,8 +170,7 @@ include::get-started:partial$get-started-serverless.adoc[] === Dedicated -With Dedicated clusters, you host your data on Redpanda Cloud resources (AWS, GCP, or Azure), and Redpanda handles provisioning, operations, and maintenance. Dedicated clusters are single-tenant deployments that support private networking (for example, VPC peering to talk over private IPs) for better data isolation. -When you create a Dedicated cluster, you select the supported xref:reference:tiers/dedicated-tiers.adoc[tier] that meets your compute and storage needs. +With Dedicated clusters, you host your data on Redpanda Cloud resources (AWS, GCP, or Azure), and Redpanda handles provisioning, operations, and maintenance. When you create a Dedicated cluster, you select the supported xref:reference:tiers/dedicated-tiers.adoc[tier] that meets your compute and storage needs. ==== Sign up for Dedicated @@ -229,6 +222,7 @@ Serverless clusters are a good fit for the following use cases: Consider BYOC or Dedicated if you need more control over the deployment or if you have workloads with consistently-high throughput. BYOC and Dedicated clusters offer the following features: +* Redpanda Agentic Data Plane (ADP): BYOC only * Multiple availability zones (AZs). A multi-AZ cluster provides higher resiliency in the event of a failure in one of the zones. * Role-based access control (RBAC) in the data plane * Kafka Connect From cc149b968284f03a80ee95cbd1a097961b8c0a8a Mon Sep 17 00:00:00 2001 From: Kat Batuigas Date: Tue, 27 Jan 2026 23:15:16 -0800 Subject: [PATCH 67/97] Apply suggestions from SME review --- .../ai-agents/pages/observability/view-transcripts.adoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/observability/view-transcripts.adoc b/modules/ai-agents/pages/observability/view-transcripts.adoc index 04affec79..f21ee804e 100644 --- a/modules/ai-agents/pages/observability/view-transcripts.adoc +++ b/modules/ai-agents/pages/observability/view-transcripts.adoc @@ -27,6 +27,8 @@ For basic orientation on agent and MCP server monitoring, see xref:ai-agents:age Use the timeline visualization to quickly identify when errors began or patterns changed, and navigate directly to transcripts from particular timestamps. +When viewing time periods with many transcripts (hundreds or thousands), the timeline displays a subset of the data to maintain performance and usability. The timeline bar indicates the actual time range of currently visible data, which may be narrower than your selected range. + TIP: See xref:ai-agents:agents/monitor-agents.adoc[] and xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] to learn basic execution patterns and health indicators to investigate. === Search and filter for transcripts @@ -43,7 +45,7 @@ The search functionality helps you find transcripts by operation names, span typ ==== Filter by service -Service filtering shows only transcripts from specific agents or MCP servers using the `service.name` resource attribute. See xref:ai-agents:observability/concepts.adoc#cross-service-transcripts[] to understand how transcripts span multiple services. +Service filtering shows only transcripts from specific agents or MCP servers using the `service.name` resource attribute. See xref:ai-agents:observability/concepts.adoc#cross-service-transcripts[Cross-service transcripts] to understand how transcripts span multiple services. * View executions from a single agent when multiple are running (service name: `ai-agent`) * Isolate MCP server activity from agent activity (service name: `mcp-{server-id}`) @@ -71,6 +73,10 @@ TIP: Apply broad filters first (time range, service) to reduce the transcript se Each row in the transcript table represents a high-level agent or MCP server request flow. Expand each parent span to see the xref:ai-agents:observability/concepts.adoc#agent-transcript-hierarchy[hierarchical structure] of nested operations, including tool calls, LLM interactions, and internal processing steps. Parent-child spans show how operations relate: for example, an agent invocation (parent) triggers LLM calls and tool executions (children). +When agents invoke remote MCP servers, transcripts fold together across service boundaries to provide a unified view of the complete operation. The trace ID originates at the initial request touchpoint and propagates across all involved services, linking spans from both the agent and MCP server under a single transcript. Use the tree view to follow the trace flow across multiple services and understand the complete request lifecycle. + +If you use external agents that directly invoke MCP servers in the Redpanda Agentic Data Plane, you may only see MCP-level parent transcripts, unless you have configured the agents to also emit traces to the Redpanda OTEL ingestion pipeline. + Selected spans display detailed information at multiple levels, from high-level summaries to complete raw data: * Start with summary view for quick assessment From 100b3147f5f306cb9594bfe83552db18564fddca Mon Sep 17 00:00:00 2001 From: micheleRP Date: Wed, 28 Jan 2026 13:58:32 -0700 Subject: [PATCH 68/97] minor edit --- modules/get-started/pages/cloud-overview.adoc | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/get-started/pages/cloud-overview.adoc b/modules/get-started/pages/cloud-overview.adoc index 9e2a4d2a9..031d2cc74 100644 --- a/modules/get-started/pages/cloud-overview.adoc +++ b/modules/get-started/pages/cloud-overview.adoc @@ -8,13 +8,9 @@ Redpanda Cloud is a complete data streaming and agentic data plane platform deli Redpanda ADP is an enterprise-grade infrastructure for building, deploying, and managing AI agents at scale. Redpanda ADP provides unified governance, observability, and security for agentic applications while leveraging Redpanda's streaming and analytical capabilities as the foundational data fabric. -Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. All components use standardized protocols for seamless interoperability. - NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. -=== What is Redpanda ADP? - -Redpanda ADP combines multiple components into a cohesive platform built on open standards for maximum interoperability: +Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. * **AI agents**: Deploy declarative agents or bring your own agent frameworks (LangChain, LlamaIndex, and others). Build multi-agent systems where specialized sub-agents handle specific responsibilities, following single-responsibility principles. * **MCP servers**: Build lightweight data and action interfaces using a low-code framework based on xref:develop:connect/about.adoc[Redpanda Connect]. Connect to hundreds of data sources (databases, cloud storage, APIs) and enforce fine-grained policies that programmatically prevent prompt injection and SQL injection attacks. MCP servers are extremely lightweight—run dozens on minimal resources—with OIDC-based access control and real-time debugging capabilities. From 6d22dba51743f3725359dd84608ab6ed0d719ee6 Mon Sep 17 00:00:00 2001 From: Paulo Borges Date: Wed, 28 Jan 2026 21:38:15 -0300 Subject: [PATCH 69/97] fix nav --- modules/ROOT/nav.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 5e6830d9d..489026742 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -86,7 +86,6 @@ ***** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] **** xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[Monitor MCP Servers] ***** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] -***** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] **** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] *** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] **** xref:ai-agents:mcp/local/overview.adoc[Overview] From a25425de4268905dc106e7f1755851b58a765fa3 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Wed, 28 Jan 2026 17:52:54 -0700 Subject: [PATCH 70/97] minor edit --- modules/get-started/pages/cloud-overview.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/get-started/pages/cloud-overview.adoc b/modules/get-started/pages/cloud-overview.adoc index 031d2cc74..b28376ae5 100644 --- a/modules/get-started/pages/cloud-overview.adoc +++ b/modules/get-started/pages/cloud-overview.adoc @@ -8,7 +8,7 @@ Redpanda Cloud is a complete data streaming and agentic data plane platform deli Redpanda ADP is an enterprise-grade infrastructure for building, deploying, and managing AI agents at scale. Redpanda ADP provides unified governance, observability, and security for agentic applications while leveraging Redpanda's streaming and analytical capabilities as the foundational data fabric. -NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. +NOTE: The Agentic Data Plane is supported on BYOC clusters running Redpanda version 25.3 and later. Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. From 40c2bbb23e9e41642ca1b4a6ec48770de4052885 Mon Sep 17 00:00:00 2001 From: Jake Cahill <45230295+JakeSCahill@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:15:59 +0000 Subject: [PATCH 71/97] Apply suggestion from @JakeSCahill --- modules/ai-agents/pages/agents/a2a-concepts.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/agents/a2a-concepts.adoc b/modules/ai-agents/pages/agents/a2a-concepts.adoc index 9a665b0ac..7422303a9 100644 --- a/modules/ai-agents/pages/agents/a2a-concepts.adoc +++ b/modules/ai-agents/pages/agents/a2a-concepts.adoc @@ -45,7 +45,7 @@ For example, if your agent URL is `\https://my-agent.ai-agents.abc123.cloud.redp The `.well-known` path follows internet standards for service discovery, making agents discoverable without configuration. -To configure these fields, see xref:ai-agents:agents/create-agent.adoc#configure-a2a-discovery-metadata-optional[Configure A2A discovery metadata]. +To configure the agent card, see xref:ai-agents:agents/create-agent.adoc#configure-a2a-discovery-metadata-optional[Configure A2A discovery metadata]. == Where A2A is used in Redpanda Cloud From 1cf01c3b362099618760631cf2589aade002034e Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Thu, 29 Jan 2026 12:20:34 +0000 Subject: [PATCH 72/97] Fix example --- .../ai-agents/examples/pipelines/fraud-detection-routing.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/examples/pipelines/fraud-detection-routing.yaml b/modules/ai-agents/examples/pipelines/fraud-detection-routing.yaml index 452eacebb..ea5f3f982 100644 --- a/modules/ai-agents/examples/pipelines/fraud-detection-routing.yaml +++ b/modules/ai-agents/examples/pipelines/fraud-detection-routing.yaml @@ -32,7 +32,7 @@ pipeline: Return JSON: { "fraud_score": 0-100, "reason": "explanation", "recommend_block": true/false } result_map: | root = this - root.fraud_analysis = content() + root.fraud_analysis = content().parse_json().catch({}) - mapping: | root = this From 28a21e58d4281f1936ee91c59e98fe48c3a732d9 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Thu, 29 Jan 2026 12:44:37 +0000 Subject: [PATCH 73/97] Fix code review feedback - Parse agent JSON response in fraud-detection-routing pipeline - Add .value accessor to rpk jq filter - Standardize country codes to ISO alpha-2 format - Make model references evergreen - Add Content-Type headers to HTTP POST requests - Normalize MCP tool response structures - Add low/none severity counts to risk indicators - Ensure consistent response schema in fallback branches --- .../mcp-tools/inputs/event_driven_workflow.yaml | 4 ++++ .../mcp-tools/processors/get_order_status.yaml | 10 ++++------ .../mcp-tools/processors/get_risk_indicators.yaml | 6 ++++++ .../mcp-tools/processors/get_transaction_details.yaml | 8 ++++---- .../examples/mcp-tools/processors/verify_merchant.yaml | 7 +++++++ modules/ai-agents/pages/agents/troubleshooting.adoc | 6 ++---- .../pages/mcp/remote/monitor-mcp-servers.adoc | 2 +- 7 files changed, 28 insertions(+), 15 deletions(-) diff --git a/modules/ai-agents/examples/mcp-tools/inputs/event_driven_workflow.yaml b/modules/ai-agents/examples/mcp-tools/inputs/event_driven_workflow.yaml index 53dadb15b..f549f88a0 100644 --- a/modules/ai-agents/examples/mcp-tools/inputs/event_driven_workflow.yaml +++ b/modules/ai-agents/examples/mcp-tools/inputs/event_driven_workflow.yaml @@ -20,12 +20,16 @@ redpanda: - http: url: "${secrets.INVENTORY_API}/reserve" verb: POST + headers: + Content-Type: application/json body: '{"order_id": "${! this.order_id }", "items": ${! json("items") }}' - check: this.event_type == "payment_confirmed" processors: - http: url: "${secrets.FULFILLMENT_API}/ship" verb: POST + headers: + Content-Type: application/json body: '{"order_id": "${! this.order_id }"}' # end::component[] diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_order_status.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_order_status.yaml index 7e78f8ca9..55c962761 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/get_order_status.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/get_order_status.yaml @@ -1,9 +1,7 @@ label: get_order_status - -processors: - - mapping: | - let order_id = this.order_id - root = if $order_id == "ORD-12345" { +mapping: | + let order_id = this.order_id + root = if $order_id == "ORD-12345" { { "order_id": $order_id, "status": "shipped", @@ -23,7 +21,7 @@ processors: } } else if $order_id == "ORD-99999" { { - "error": true, + "error": "order_not_found", "message": "Order not found" } } else { diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_risk_indicators.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_risk_indicators.yaml index 410990dd2..c4ccdf19d 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/get_risk_indicators.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/get_risk_indicators.yaml @@ -74,6 +74,8 @@ mapping: | "critical_count": 0, "high_count": 0, "medium_count": 2, + "low_count": 1, + "none_count": 0, "overall_assessment": "medium_fraud_probability" }, this.transaction_id == "TXN-89015" => { @@ -99,6 +101,8 @@ mapping: | "critical_count": 0, "high_count": 0, "medium_count": 0, + "low_count": 1, + "none_count": 2, "overall_assessment": "low_fraud_probability" }, _ => { @@ -108,6 +112,8 @@ mapping: | "critical_count": 0, "high_count": 0, "medium_count": 0, + "low_count": 0, + "none_count": 0, "overall_assessment": "insufficient_data" } } diff --git a/modules/ai-agents/examples/mcp-tools/processors/get_transaction_details.yaml b/modules/ai-agents/examples/mcp-tools/processors/get_transaction_details.yaml index 1943fbcd6..82b44ba6f 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/get_transaction_details.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/get_transaction_details.yaml @@ -9,7 +9,7 @@ mapping: | "merchant": { "name": "LUXURY WATCHES INT", "category": "jewelry", - "country": "Singapore", + "country": "SG", "mcc": "5944" }, "card_last_four": "4532", @@ -29,7 +29,7 @@ mapping: | "merchant": { "name": "EXAMPLE MKTPLACE", "category": "online_retail", - "country": "USA", + "country": "US", "mcc": "5942" }, "card_last_four": "4532", @@ -49,7 +49,7 @@ mapping: | "merchant": { "name": "EXAMPLE STREAMING", "category": "subscription_service", - "country": "USA", + "country": "US", "mcc": "4899" }, "card_last_four": "8821", @@ -70,7 +70,7 @@ mapping: | "merchant": { "name": "HOTEL PARIS", "category": "lodging", - "country": "France", + "country": "FR", "mcc": "7011" }, "card_last_four": "2193", diff --git a/modules/ai-agents/examples/mcp-tools/processors/verify_merchant.yaml b/modules/ai-agents/examples/mcp-tools/processors/verify_merchant.yaml index d35ddda8c..e0ad87731 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/verify_merchant.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/verify_merchant.yaml @@ -103,6 +103,13 @@ mapping: | "reputation_score": 50, "reputation_level": "unknown", "verification_status": "not_found", + "fraud_reports": { + "total_reports": 0, + "recent_reports_30d": 0, + "confirmed_fraud_cases": 0 + }, + "business_details": {}, + "red_flags": [], "message": "Merchant not found in verification database", "recommendation": "manual_review_required" } diff --git a/modules/ai-agents/pages/agents/troubleshooting.adoc b/modules/ai-agents/pages/agents/troubleshooting.adoc index eaad03967..69eda27eb 100644 --- a/modules/ai-agents/pages/agents/troubleshooting.adoc +++ b/modules/ai-agents/pages/agents/troubleshooting.adoc @@ -60,7 +60,7 @@ NEVER respond about order status without calling the tool first. ---- . Review tool descriptions in your MCP server configuration. -. Use a more capable model (GPT-5.2, Claude Sonnet 4.5, or equivalent). +. Use a more capable model from the supported list for your gateway. . Increase max iterations if the agent is stopping before reaching tools. **Prevention:** @@ -216,9 +216,7 @@ Diagnose and fix issues related to agent speed and resource consumption. **Solution:** -. Use a faster model for simple queries: -.. GPT-5 Mini for straightforward tasks -.. Reserve larger models for complex reasoning +. Use a faster, lower-latency model tier for simple queries and reserve larger models for complex reasoning. . Review conversation history in the *Inspector* tab to identify unnecessary tool calls. . Optimize tool implementations: .. Add caching where appropriate diff --git a/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc index 20813596a..7966fc3ae 100644 --- a/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc @@ -71,7 +71,7 @@ Example: Find all invocations of a specific tool: [,bash] ---- rpk topic consume redpanda.otel_traces --offset start \ - | jq 'select(.instrumentationScope.name == "rpcn-mcp" and .name == "weather")' + | jq '.value | select(.instrumentationScope.name == "rpcn-mcp" and .name == "weather")' ---- === Measure performance From 71b1d19f38acfbc67b01710e8a66e456c3a66f2a Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Thu, 29 Jan 2026 13:02:30 +0000 Subject: [PATCH 74/97] Apply suggestions --- .../examples/mcp-tools/processors/openai_chat.yaml | 2 +- .../mcp-tools/processors/transform_validate.yaml | 2 +- .../examples/mcp-tools/snippets/defaults.yaml | 4 ++++ .../agents/tutorials/customer-support-agent.adoc | 2 +- .../tutorials/transaction-dispute-resolution.adoc | 11 +++-------- modules/ai-agents/pages/mcp/remote/create-tool.adoc | 2 +- modules/develop/pages/connect/cookbooks/jira.adoc | 3 ++- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/ai-agents/examples/mcp-tools/processors/openai_chat.yaml b/modules/ai-agents/examples/mcp-tools/processors/openai_chat.yaml index fa5e81c63..b8c202a66 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/openai_chat.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/openai_chat.yaml @@ -12,7 +12,7 @@ openai_chat_completion: 2. Key themes 3. Actionable insights - Feedback: ${! json("feedback_text") } + Feedback: ${! json(feedback_text) } max_tokens: 500 # end::component[] diff --git a/modules/ai-agents/examples/mcp-tools/processors/transform_validate.yaml b/modules/ai-agents/examples/mcp-tools/processors/transform_validate.yaml index d7b38dab3..b05b619ad 100644 --- a/modules/ai-agents/examples/mcp-tools/processors/transform_validate.yaml +++ b/modules/ai-agents/examples/mcp-tools/processors/transform_validate.yaml @@ -14,7 +14,7 @@ processors: root.is_premium = this.subscription_tier == "premium" # Filter sensitive data - root.profile = this.profile.without("ssn", "credit_card") + root.profile = this.profile.or({}).without("ssn", "credit_card") # end::mapping[] meta: diff --git a/modules/ai-agents/examples/mcp-tools/snippets/defaults.yaml b/modules/ai-agents/examples/mcp-tools/snippets/defaults.yaml index 2360420bc..844ea3326 100644 --- a/modules/ai-agents/examples/mcp-tools/snippets/defaults.yaml +++ b/modules/ai-agents/examples/mcp-tools/snippets/defaults.yaml @@ -17,4 +17,8 @@ properties: type: string description: "Temperature units: 'metric' or 'imperial' (default: metric)" required: false + - name: limit + type: number + description: "Max results (default: 10)" + required: false # end::properties[] diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index e4aa109cb..7e188f652 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -213,7 +213,7 @@ Click *Clear context* to clear the conversation history. Then enter this query: Where is my order? ---- -The agent recognizes the request is missing an order ID and asks the customer to provide it. Watch the conversation panel—the agent calls zero tools. Instead of guessing or fabricating information, it asks a clarifying question. +The agent recognizes the request is missing an order ID and asks the customer to provide it. Watch the conversation panel and see that the agent calls zero tools. Instead of guessing or fabricating information, it asks a clarifying question. This demonstrates pre-condition checking. Effective orchestration includes knowing when NOT to invoke tools. diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index 4b421c2cd..a5edd0434 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -24,9 +24,9 @@ These patterns apply beyond banking to any domain requiring specialized expertis Banks handle thousands of dispute calls daily. Customers report unauthorized charges, billing errors, or unrecognized transactions. Each investigation requires cross-referencing multiple systems and applying consistent fraud detection logic. -Traditionally, human agents manually open multiple systems, cross-reference data, and take notes—a 10-15 minute process prone to inconsistencies and incomplete compliance logging. +Traditionally, human agents manually open multiple systems, cross-reference data, and take notes. A 10-15 minute process prone to inconsistencies and incomplete compliance logging. -Multi-agent automation transforms this workflow by enabling instant data aggregation from all sources, consistent logic applied every time, 30-60 second resolution for simple cases, and structured results for compliance. Human agents handle only complex escalations. +Multi-agent automation transforms this workflow by enabling instant data aggregation from all sources, consistent logic applied every time, 10-15 second resolution, and structured results for compliance. Human agents handle only complex escalations. When a customer calls saying "I see a $247.83 charge from 'ACME CORP' but I never shopped there. Is this fraud?", the system must investigate account history, calculate fraud scores, verify merchant legitimacy, and make a recommendation with structured results. @@ -215,7 +215,7 @@ The root agent orchestrates sub-agents and makes final recommendations. You'll c [IMPORTANT] ==== -Sub-agents inherit the LLM provider, model, resource tier, and max iterations from the root agent. This tutorial uses GPT-5 Mini and max iterations of 15 to optimize performance. Using slower models (GPT-5.2, Claude Sonnet 4.5) or high max iterations (50+) will cause sub-agents to execute slowly—each sub-agent call could take 60-90 seconds instead of 10-15 seconds. +Sub-agents inherit the LLM provider, model, resource tier, and max iterations from the root agent. This tutorial uses GPT-5 Mini and max iterations of 15 to optimize performance. Using slower models (GPT-5.2, Claude Sonnet 4.5) or high max iterations (50+) will cause sub-agents to execute slowly. Each sub-agent call could take 60-90 seconds instead of 10-15 seconds. ==== . Go to *Agentic AI* > *AI Agents*. @@ -229,11 +229,6 @@ Sub-agents inherit the LLM provider, model, resource tier, and max iterations fr * *Model*: GPT-5 Mini (fast, cost-effective for structured workflows) * *API Key*: Your LLM provider API key * *Max Iterations*: 15 -+ -[NOTE] -==== -This tutorial uses GPT-5 Mini and reduced max iterations to optimize for tutorial speed. Multi-agent investigations typically complete in 30-90 seconds with these settings. For production deployments handling complex edge cases, consider GPT-5.2 or Claude Sonnet 4.5 with higher max iterations. -==== . In the *System Prompt* field, enter: + diff --git a/modules/ai-agents/pages/mcp/remote/create-tool.adoc b/modules/ai-agents/pages/mcp/remote/create-tool.adoc index d856b8607..3b13dcec4 100644 --- a/modules/ai-agents/pages/mcp/remote/create-tool.adoc +++ b/modules/ai-agents/pages/mcp/remote/create-tool.adoc @@ -6,7 +6,7 @@ // Learning objectives - what readers can do after reading this page: :learning-objective-1: Create a tool with the correct structure and MCP metadata :learning-objective-2: Map MCP parameters to component configuration fields using Bloblang -:learning-objective-3: Test tools using the MCP inspector +:learning-objective-3: Test tools using the MCP Inspector After xref:ai-agents:mcp/remote/quickstart.adoc[deploying your first MCP server], create custom tools that AI clients can discover and invoke. This guide walks you through the process using any Redpanda Connect component. diff --git a/modules/develop/pages/connect/cookbooks/jira.adoc b/modules/develop/pages/connect/cookbooks/jira.adoc index f00490226..f8015ae1d 100644 --- a/modules/develop/pages/connect/cookbooks/jira.adoc +++ b/modules/develop/pages/connect/cookbooks/jira.adoc @@ -7,7 +7,8 @@ :learning-objective-2: Combine generate input with Jira processor for scheduled queries :learning-objective-3: Create Jira issues using the HTTP processor and REST API -The Jira processor enables querying Jira issues using JQL (Jira Query Language) and returning structured data. Unlike traditional connectors, the Jira processor only supports queries and can be used as both an input (with `generate`) and an output (with `drop`). +The Jira processor enables querying Jira issues using JQL (Jira Query Language) and returning structured data. It’s a processor, so you can use it in pipelines for input-style flows (pair with `generate`) or output-style flows (pair with `drop`). + Use this cookbook to: From ea9910aa52c6a80685846a0a3e4352bd552bbfd6 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Thu, 29 Jan 2026 13:05:08 +0000 Subject: [PATCH 75/97] Apply suggestions --- .../pages/agents/tutorials/transaction-dispute-resolution.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index a5edd0434..0e6e08ce9 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -516,7 +516,7 @@ The Service Account is required for the `a2a_message` processor to authenticate . Name the pipeline `dispute-pipeline`. . Paste this configuration: + -[,yaml] +[,yaml,role="no-placeholders"] ---- include::ai-agents:example$pipelines/dispute-pipeline.yaml[] ---- From 20ccc4ce1291c175a0f609ccbaae231c901be5be Mon Sep 17 00:00:00 2001 From: micheleRP Date: Thu, 29 Jan 2026 10:35:31 -0700 Subject: [PATCH 76/97] AWS only & what's new blurb --- modules/get-started/pages/cloud-overview.adoc | 4 ++-- modules/get-started/pages/whats-new-cloud.adoc | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/get-started/pages/cloud-overview.adoc b/modules/get-started/pages/cloud-overview.adoc index b28376ae5..39d583615 100644 --- a/modules/get-started/pages/cloud-overview.adoc +++ b/modules/get-started/pages/cloud-overview.adoc @@ -8,9 +8,9 @@ Redpanda Cloud is a complete data streaming and agentic data plane platform deli Redpanda ADP is an enterprise-grade infrastructure for building, deploying, and managing AI agents at scale. Redpanda ADP provides unified governance, observability, and security for agentic applications while leveraging Redpanda's streaming and analytical capabilities as the foundational data fabric. -NOTE: The Agentic Data Plane is supported on BYOC clusters running Redpanda version 25.3 and later. +NOTE: The Agentic Data Plane is supported on BYOC clusters running with AWS and Redpanda version 25.3 and later. -Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. +Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. It includes the following key components: * **AI agents**: Deploy declarative agents or bring your own agent frameworks (LangChain, LlamaIndex, and others). Build multi-agent systems where specialized sub-agents handle specific responsibilities, following single-responsibility principles. * **MCP servers**: Build lightweight data and action interfaces using a low-code framework based on xref:develop:connect/about.adoc[Redpanda Connect]. Connect to hundreds of data sources (databases, cloud storage, APIs) and enforce fine-grained policies that programmatically prevent prompt injection and SQL injection attacks. MCP servers are extremely lightweight—run dozens on minimal resources—with OIDC-based access control and real-time debugging capabilities. diff --git a/modules/get-started/pages/whats-new-cloud.adoc b/modules/get-started/pages/whats-new-cloud.adoc index db4d78403..049eae167 100644 --- a/modules/get-started/pages/whats-new-cloud.adoc +++ b/modules/get-started/pages/whats-new-cloud.adoc @@ -6,6 +6,20 @@ This page lists new features added to Redpanda Cloud. +== February 2026 + +=== Agentic Data Plane (ADP) + +Redpanda ADP is an enterprise-grade infrastructure for building, deploying, and managing AI agents at scale. Redpanda ADP provides unified governance, observability, and security for agentic applications while leveraging Redpanda's streaming and analytical capabilities as the foundational data fabric. + +NOTE: The Agentic Data Plane is supported on BYOC clusters running with AWS and Redpanda version 25.3 and later. + +Redpanda ADP is built on open standards and protocols, allowing you to pick and choose components that fit your needs. Integrate with existing agent frameworks, data processing systems, or custom code. It includes the following key components: + +* **AI agents**: Deploy declarative agents or bring your own agent frameworks (LangChain, LlamaIndex, and others). Build multi-agent systems where specialized sub-agents handle specific responsibilities, following single-responsibility principles. +* **MCP servers**: Build lightweight data and action interfaces using a low-code framework based on xref:develop:connect/about.adoc[Redpanda Connect]. Connect to hundreds of data sources (databases, cloud storage, APIs) and enforce fine-grained policies that programmatically prevent prompt injection and SQL injection attacks. MCP servers are extremely lightweight—run dozens on minimal resources—with OIDC-based access control and real-time debugging capabilities. +* **AI Gateway**: Manage LLM provider access with cost controls, rate limiting, intelligent routing, and failover support across multiple providers. + == January 2026 === Redpanda Connect updates From 000b819d8af81ca57e7922e68c1af8ca001fdae7 Mon Sep 17 00:00:00 2001 From: micheleRP Date: Thu, 29 Jan 2026 10:36:27 -0700 Subject: [PATCH 77/97] AWS only --- modules/ai-agents/partials/ai-gateway-byoc-note.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/partials/ai-gateway-byoc-note.adoc b/modules/ai-agents/partials/ai-gateway-byoc-note.adoc index 5d3b58cf0..86fdf86a6 100644 --- a/modules/ai-agents/partials/ai-gateway-byoc-note.adoc +++ b/modules/ai-agents/partials/ai-gateway-byoc-note.adoc @@ -1 +1 @@ -NOTE: AI Gateway is supported on BYOC clusters running Redpanda version 25.3 and later. +NOTE: The Agentic Data Plane is supported on BYOC clusters running with AWS and Redpanda version 25.3 and later. From 6bae27fbb878a2a984d2404a870126779c7a67e2 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Fri, 30 Jan 2026 17:25:54 +0000 Subject: [PATCH 78/97] Apply feedback --- modules/ROOT/nav.adoc | 36 ++- .../examples/mcp-tools/test-mcp-tools.sh | 2 +- .../ai-agents/examples/test-mcp-examples.sh | 221 +++++++++++++ modules/ai-agents/examples/testing.adoc | 290 ++++++++++++++++++ .../ai-agents/pages/agents/a2a-concepts.adoc | 2 +- .../agents/agent-to-agent-integration.adoc | 126 ++++++++ .../pages/agents/architecture-patterns.adoc | 2 +- .../ai-agents/pages/agents/create-agent.adoc | 8 +- .../agents/external-app-integration.adoc | 101 ++++++ .../pages/agents/external-integration.adoc | 94 ++++++ .../pages/agents/monitor-agents.adoc | 2 +- .../ai-agents/pages/agents/quickstart.adoc | 2 +- .../tutorials/customer-support-agent.adoc | 14 +- .../transaction-dispute-resolution.adoc | 12 +- .../pages/mcp/remote/create-tool.adoc | 8 +- .../pages/mcp/remote/manage-servers.adoc | 16 +- .../pages/mcp/remote/monitor-activity.adoc | 113 +++++++ .../pages/mcp/remote/monitor-mcp-servers.adoc | 6 +- .../pages/mcp/remote/quickstart.adoc | 6 +- .../pages/mcp/remote/scale-resources.adoc | 4 +- .../pages/observability/monitor-agents.adoc | 226 ++++++++++++++ .../configuration/resource-management.adoc | 4 +- .../configuration/secret-management.adoc | 8 +- 23 files changed, 1238 insertions(+), 65 deletions(-) create mode 100644 modules/ai-agents/examples/test-mcp-examples.sh create mode 100644 modules/ai-agents/examples/testing.adoc create mode 100644 modules/ai-agents/pages/agents/agent-to-agent-integration.adoc create mode 100644 modules/ai-agents/pages/agents/external-app-integration.adoc create mode 100644 modules/ai-agents/pages/agents/external-integration.adoc create mode 100644 modules/ai-agents/pages/mcp/remote/monitor-activity.adoc create mode 100644 modules/ai-agents/pages/observability/monitor-agents.adoc diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index b5bfd07b3..d25d4f013 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -24,23 +24,6 @@ ** xref:get-started:cluster-types/create-dedicated-cloud-cluster.adoc[] * xref:ai-agents:index.adoc[Agentic AI] -** xref:ai-agents:agents/index.adoc[Agents] -*** xref:ai-agents:agents/get-started-index.adoc[Get Started] -**** xref:ai-agents:agents/overview.adoc[Overview] -**** xref:ai-agents:agents/concepts.adoc[Concepts] -**** xref:ai-agents:agents/quickstart.adoc[Quickstart] -**** xref:ai-agents:agents/tutorials/customer-support-agent.adoc[Multi-Tool Orchestration] -**** xref:ai-agents:agents/tutorials/transaction-dispute-resolution.adoc[Multi-Agent Systems] -*** xref:ai-agents:agents/build-index.adoc[Build Agents] -**** xref:ai-agents:agents/create-agent.adoc[Create an Agent] -**** xref:ai-agents:agents/prompt-best-practices.adoc[System Prompt Best Practices] -**** xref:ai-agents:agents/architecture-patterns.adoc[Architecture Patterns] -**** xref:ai-agents:agents/troubleshooting.adoc[Troubleshoot] -*** xref:ai-agents:agents/monitor-agents.adoc[Monitor Agents] -*** xref:ai-agents:agents/integration-index.adoc[Agent Integrations] -**** xref:ai-agents:agents/integration-overview.adoc[Integration Patterns] -**** xref:ai-agents:agents/pipeline-integration-patterns.adoc[Pipeline to Agent] -**** xref:ai-agents:agents/a2a-concepts.adoc[A2A Protocol] ** xref:ai-agents:mcp/index.adoc[MCP] *** xref:ai-agents:mcp/overview.adoc[Overview] *** xref:ai-agents:mcp/remote/index.adoc[Remote MCP] @@ -58,6 +41,25 @@ **** xref:ai-agents:mcp/local/overview.adoc[Overview] **** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] **** xref:ai-agents:mcp/local/configuration.adoc[Configure] + +** xref:ai-agents:agents/index.adoc[Agents] +*** xref:ai-agents:agents/get-started-index.adoc[Get Started] +**** xref:ai-agents:agents/overview.adoc[Overview] +**** xref:ai-agents:agents/concepts.adoc[Concepts] +**** xref:ai-agents:agents/quickstart.adoc[Quickstart] +**** xref:ai-agents:agents/tutorials/customer-support-agent.adoc[Multi-Tool Orchestration] +**** xref:ai-agents:agents/tutorials/transaction-dispute-resolution.adoc[Multi-Agent Systems] +*** xref:ai-agents:agents/build-index.adoc[Build Agents] +**** xref:ai-agents:agents/create-agent.adoc[Create an Agent] +**** xref:ai-agents:agents/prompt-best-practices.adoc[System Prompt Best Practices] +**** xref:ai-agents:agents/architecture-patterns.adoc[Architecture Patterns] +**** xref:ai-agents:agents/troubleshooting.adoc[Troubleshoot] +*** xref:ai-agents:agents/monitor-agents.adoc[Monitor Agents] +*** xref:ai-agents:agents/integration-index.adoc[Agent Integrations] +**** xref:ai-agents:agents/integration-overview.adoc[Integration Patterns] +**** xref:ai-agents:agents/pipeline-integration-patterns.adoc[Pipeline to Agent] +**** xref:ai-agents:agents/a2a-concepts.adoc[A2A Protocol] + ** xref:ai-agents:observability/concepts.adoc[Transcripts] * xref:develop:connect/about.adoc[Redpanda Connect] diff --git a/modules/ai-agents/examples/mcp-tools/test-mcp-tools.sh b/modules/ai-agents/examples/mcp-tools/test-mcp-tools.sh index f815a561f..263ea5dbf 100755 --- a/modules/ai-agents/examples/mcp-tools/test-mcp-tools.sh +++ b/modules/ai-agents/examples/mcp-tools/test-mcp-tools.sh @@ -12,7 +12,7 @@ # # Unlike rp-connect-docs, Cloud MCP tools cannot be tested with # `rpk connect run` because they are standalone tool definitions, not -# full pipelines. End-to-end testing requires the Cloud Console. +# full pipelines. End-to-end testing requires the Cloud UI. set -euo pipefail diff --git a/modules/ai-agents/examples/test-mcp-examples.sh b/modules/ai-agents/examples/test-mcp-examples.sh new file mode 100644 index 000000000..54d6495c0 --- /dev/null +++ b/modules/ai-agents/examples/test-mcp-examples.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash +# +# Test script for Redpanda Cloud MCP examples +# +# This script tests: +# 1. MCP tool definitions using `rpk connect mcp-server lint` +# 2. MCP metadata validation (enabled, description, properties) +# +# Usage: +# ./test-mcp-examples.sh # Run all tests +# ./test-mcp-examples.sh --lint-only # Only lint, skip metadata validation +# +# Unlike rp-connect-docs, Cloud MCP tools cannot be tested with +# `rpk connect run` because they are standalone tool definitions, not +# full pipelines. End-to-end testing requires the Cloud UI. + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Get script directory and set MCP tools directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MCP_TOOLS_DIR="$SCRIPT_DIR/mcp-tools" +cd "$MCP_TOOLS_DIR" + +# Counters +TOTAL_TOOLS=0 +PASSED_LINT=0 +PASSED_METADATA=0 +FAILED_LINT=0 +FAILED_METADATA=0 +SKIPPED=0 + +echo "🧪 Redpanda Cloud MCP Examples - Test Suite" +echo "============================================" +echo "" + +# Parse arguments +RUN_METADATA=true + +if [[ $# -gt 0 ]]; then + case "$1" in + --lint-only) + RUN_METADATA=false + ;; + esac +fi + +# ============================================================================ +# SECTION 1: MCP Tool Linting +# Validates YAML syntax and component schemas +# ============================================================================ + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo -e "📦 ${CYAN}SECTION 1: MCP Tool Linting${NC}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Count YAML files +file_count=$(find . -maxdepth 1 -name "*.yaml" | wc -l | tr -d ' ') +TOTAL_TOOLS=$file_count + +echo -n -e "${BLUE}📁 mcp-tools/${NC} ($file_count files)... " + +if output=$(rpk connect mcp-server lint --skip-env-var-check . 2>&1); then + echo -e "${GREEN}✓ PASSED${NC}" + PASSED_LINT=$file_count +else + echo -e "${RED}✗ FAILED${NC}" + echo "$output" | sed 's/^/ /' | head -20 + FAILED_LINT=$file_count +fi + +# ============================================================================ +# SECTION 2: MCP Metadata Validation +# Validates tool metadata (enabled, description, properties) +# ============================================================================ + +if $RUN_METADATA; then + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -e "📝 ${CYAN}SECTION 2: MCP Metadata Validation${NC}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + # Determine which YAML parser to use + use_yq=true + if ! command -v yq &> /dev/null; then + use_yq=false + if ! command -v python3 &> /dev/null; then + echo -e "${YELLOW}⚠ Neither yq nor python3 available - skipping metadata validation${NC}" + RUN_METADATA=false + fi + fi + + if $RUN_METADATA; then + for file in *.yaml; do + if [[ -f "$file" ]]; then + echo -n -e " ${BLUE}$file${NC}... " + + # Check if .meta.mcp exists + if $use_yq; then + mcp_exists=$(yq eval '.meta.mcp' "$file" 2>/dev/null) + enabled=$(yq eval '.meta.mcp.enabled' "$file" 2>/dev/null) + description=$(yq eval '.meta.mcp.description' "$file" 2>/dev/null) + else + mcp_exists=$(python3 -c " +import yaml +try: + with open('$file') as f: + doc = yaml.safe_load(f) + meta = doc.get('meta', {}) if doc else {} + mcp = meta.get('mcp') + print('null' if mcp is None else 'exists') +except: + print('null') +" 2>/dev/null) + enabled=$(python3 -c " +import yaml +try: + with open('$file') as f: + doc = yaml.safe_load(f) + enabled = doc.get('meta', {}).get('mcp', {}).get('enabled') + print('null' if enabled is None else str(enabled).lower()) +except: + print('null') +" 2>/dev/null) + description=$(python3 -c " +import yaml +try: + with open('$file') as f: + doc = yaml.safe_load(f) + desc = doc.get('meta', {}).get('mcp', {}).get('description') + print('null' if desc is None or desc == '' else str(desc)) +except: + print('null') +" 2>/dev/null) + fi + + # Validate + if [[ "$mcp_exists" == "null" || -z "$mcp_exists" ]]; then + echo -e "${YELLOW}SKIPPED${NC} (no MCP metadata)" + SKIPPED=$((SKIPPED + 1)) + elif [[ "$enabled" != "true" ]]; then + echo -e "${YELLOW}WARNING${NC} (mcp.enabled not true)" + SKIPPED=$((SKIPPED + 1)) + elif [[ "$description" == "null" || -z "$description" ]]; then + echo -e "${RED}FAILED${NC} (missing description)" + FAILED_METADATA=$((FAILED_METADATA + 1)) + else + echo -e "${GREEN}PASSED${NC}" + PASSED_METADATA=$((PASSED_METADATA + 1)) + fi + fi + done + fi +fi + +# ============================================================================ +# SECTION 3: Cloud-Specific Validation +# Validates secrets use Cloud format (${secrets.X}) +# ============================================================================ + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo -e "☁️ ${CYAN}SECTION 3: Cloud Secrets Format${NC}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +secrets_issues=0 +for file in *.yaml; do + if [[ -f "$file" ]]; then + # Check for non-Cloud secrets patterns (${VAR} without secrets. prefix) + # Exclude: + # - ${! ... } which is Bloblang interpolation + # - ${REDPANDA_BROKERS} which is platform-injected + if grep -E '\$\{[A-Z_]+\}' "$file" | grep -v '\${secrets\.' | grep -v '\${!' | grep -v '\${REDPANDA_BROKERS}' > /dev/null 2>&1; then + echo -e " ${BLUE}$file${NC}... ${YELLOW}WARNING${NC} (uses env vars instead of \${secrets.X})" + secrets_issues=$((secrets_issues + 1)) + fi + fi +done + +if [[ $secrets_issues -eq 0 ]]; then + echo -e " ${GREEN}✓ All files use Cloud secrets format${NC}" +fi + +# ============================================================================ +# SUMMARY +# ============================================================================ + +echo "" +echo "============================================" +echo "📊 Test Summary" +echo "============================================" + +echo -e "Lint: ${PASSED_LINT}/${TOTAL_TOOLS} passed" +if $RUN_METADATA; then + METADATA_TOTAL=$((PASSED_METADATA + FAILED_METADATA + SKIPPED)) + echo -e "Metadata: ${PASSED_METADATA}/${METADATA_TOTAL} passed (${SKIPPED} skipped)" +fi +if [[ $secrets_issues -gt 0 ]]; then + echo -e "Secrets: ${YELLOW}${secrets_issues} warnings${NC}" +fi +echo "──────────────────────────────────────────────────" + +TOTAL_FAILED=$((FAILED_LINT + FAILED_METADATA)) + +if [[ $TOTAL_FAILED -gt 0 ]]; then + echo -e "${RED}❌ Some tests failed ($TOTAL_FAILED failures)${NC}" + exit 1 +else + echo -e "${GREEN}✅ All tests passed!${NC}" + exit 0 +fi diff --git a/modules/ai-agents/examples/testing.adoc b/modules/ai-agents/examples/testing.adoc new file mode 100644 index 000000000..413fa7af2 --- /dev/null +++ b/modules/ai-agents/examples/testing.adoc @@ -0,0 +1,290 @@ += Test MCP Examples +:description: Automated testing strategies for Redpanda Cloud MCP server examples. + +This document describes the automated testing strategies for Redpanda Cloud MCP server examples. + +All MCP examples are automatically tested to ensure: + +. YAML syntax and structure are correct +. MCP metadata is complete and valid +. Component schemas match Redpanda Connect specifications +. Secrets syntax uses Cloud Secrets Store format (`${secrets.X}`) + +== Testing approaches + +=== Configuration linting + +Validate MCP tool configurations using `rpk connect lint`: + +[,bash] +---- +# Lint a single MCP tool +rpk connect lint weather_service.yaml + +# Lint all examples +rpk connect lint *.yaml + +# Lint with environment variable checking skipped (recommended for MCP) +rpk connect lint --skip-env-var-check *.yaml +---- + +This checks for common issues such as: + +* YAML syntax errors +* Unknown component types +* Invalid field names +* Type mismatches +* Missing required fields + +=== MCP metadata validation + +The test script validates MCP-specific metadata for all tool examples: + +[,bash] +---- +# Run all tests (includes linting + MCP validation) +./test-mcp-examples.sh + +# Test specific files +./test-mcp-examples.sh weather_*.yaml +---- + +MCP metadata validation checks: + +* Presence of `meta.mcp` section +* `enabled: true` is set +* `description` field exists and is non-empty +* `properties` are properly structured (if present) + +=== Unit testing limitations + +MCP tool examples are standalone component definitions (`label:`, `processors:`, `meta:`), not full pipelines with `input:`, `pipeline:`, `output:` sections. This means they cannot use inline `tests:` sections like cookbook examples do. + +The `rpk connect test` command requires full pipeline structure with paths like `/pipeline/processors/0`, which don't exist in MCP tool definitions. + +For testing MCP tools: + +- Ensure syntax and schema correctness. +- Verify MCP metadata has proper description and properties. +- Perform manual testing using the Cloud UI MCP Server interface to test tools end-to-end. + +== MCP tool structure + +MCP tools are structured as standalone components: + +[,yaml] +---- +label: weather-service +processors: + - label: fetch_weather_data + http: + url: 'https://wttr.in/${! @city }?format=j1' + verb: GET + + - label: format_response + mutation: | + root = { + "city": @city, + "temperature": this.current_condition.0.temp_C.number() + } + +meta: + mcp: + enabled: true + description: "Get current weather conditions for any city worldwide" + properties: + - name: city + type: string + description: "Name of the city" + required: true +---- + +== Test script usage + +The `test-mcp-examples.sh` script provides automated validation: + +[,bash] +---- +# Test all examples +./test-mcp-examples.sh + +# Test specific files +./test-mcp-examples.sh weather_*.yaml +./test-mcp-examples.sh customer_*.yaml +---- + +The script provides color-coded output: + +[,console] +---- +🧪 Redpanda Connect MCP Examples Test Suite (Cloud) +==================================================== + +📄 Testing: weather_service.yaml + Linting weather_service.yaml... PASSED + Validating MCP metadata... PASSED + +==================================================== +📊 Test Summary +==================================================== +Total configs tested: 10 +Passed: 10 +Failed: 0 + +✅ All tests passed! +---- + +== Manual end-to-end testing + +For comprehensive validation, test MCP tools using the Cloud UI: + +. Navigate to your Cloud cluster's MCP Server configuration +. Add or update your MCP tool configuration +. Use the Cloud UI's MCP Inspector to locate your tool +. Verify the tool executes correctly and returns expected results + +This validates: + +* Tool loads correctly in the MCP server +* Tool executes with provided parameters +* Responses are formatted correctly +* Secrets are properly resolved from Cloud Secrets Store + +== GitHub Actions CI/CD + +Automated tests run on every push and pull request using GitHub Actions. + +The workflow tests all examples whenever: + +* Any `.yaml` file in `modules/ai-agents/examples/mcp-tools/` changes +* The test script itself is modified + +See `.github/workflows/test-mcp-examples.yaml` for the complete workflow. + +== Best practices + +=== Use descriptive tool names + +[,yaml] +---- +# Good +label: fetch-customer-orders + +# Bad +label: tool1 +---- + +=== Write clear MCP descriptions + +[,yaml] +---- +# Good +meta: + mcp: + description: "Fetch a customer's order history and calculate spending metrics over the last 30 days" + +# Bad +meta: + mcp: + description: "Get orders" +---- + +=== Document all properties + +[,yaml] +---- +# Good +properties: + - name: customer_id + type: string + description: "Unique identifier for the customer" + required: true + - name: days + type: number + description: "Number of days to look back (default: 30)" + required: false + +# Bad +properties: + - name: id + type: string + required: true +---- + +=== Use Cloud Secrets Store for sensitive data + +[,yaml] +---- +# Cloud format - uses Secrets Store +sql_select: + driver: "postgres" + dsn: "${secrets.POSTGRES_DSN}" + table: "customers" +---- + +=== Tag your examples + +[,yaml] +---- +meta: + tags: [ example, weather, api ] # Helps organize and filter + mcp: + enabled: true +---- + +== Adding new examples + +When adding new MCP tool examples: + +. **Create your YAML file** in `modules/ai-agents/examples/mcp-tools/`: ++ +[,bash] +---- +cd modules/ai-agents/examples +touch my-new-tool.yaml +---- + +. **Include complete MCP metadata:** ++ +[,yaml] +---- +label: my-new-tool +processors: + # Your processor configuration + +meta: + mcp: + enabled: true + description: "Clear, task-oriented description" + properties: + - name: param_name + type: string + description: "Parameter purpose and constraints" + required: true +---- + +. **Lint your example:** ++ +[,bash] +---- +rpk connect lint --skip-env-var-check my-new-tool.yaml +---- + +. **Run automated tests:** ++ +[,bash] +---- +./test-mcp-examples.sh my-new-tool.yaml +---- + +. **Test in Cloud UI (recommended):** ++ +Deploy your MCP server configuration and test the tool through the Cloud UI AI interface. + +. **Commit your example:** ++ +[,bash] +---- +git add modules/ai-agents/examples/mcp-tools/my-new-tool.yaml +git commit -m "Add my-new-tool MCP example" +---- diff --git a/modules/ai-agents/pages/agents/a2a-concepts.adoc b/modules/ai-agents/pages/agents/a2a-concepts.adoc index 7422303a9..45680aa96 100644 --- a/modules/ai-agents/pages/agents/a2a-concepts.adoc +++ b/modules/ai-agents/pages/agents/a2a-concepts.adoc @@ -39,7 +39,7 @@ The agent card is a JSON document that describes what the agent can do and how t [#agent-card-location] === Agent card location -Redpanda Cloud agents expose their agent cards at the `/.well-known/agent-card.json` subpath of the agent URL. You can find the agent URL on the agent overview page in the Redpanda Cloud Console under *Agentic AI* > *AI Agents*. +Redpanda Cloud agents expose their agent cards at the `/.well-known/agent-card.json` subpath of the agent URL. You can find the agent URL on the agent overview page in the Redpanda Cloud UI under *Agentic AI* > *AI Agents*. For example, if your agent URL is `\https://my-agent.ai-agents.abc123.cloud.redpanda.com`, your agent card URL is `\https://my-agent.ai-agents.abc123.cloud.redpanda.com/.well-known/agent-card.json`. diff --git a/modules/ai-agents/pages/agents/agent-to-agent-integration.adoc b/modules/ai-agents/pages/agents/agent-to-agent-integration.adoc new file mode 100644 index 000000000..2c7116135 --- /dev/null +++ b/modules/ai-agents/pages/agents/agent-to-agent-integration.adoc @@ -0,0 +1,126 @@ += External Agent Integration +:description: Integrate Redpanda Cloud agents with external systems using the A2A protocol. +:page-topic-type: concept +:personas: ai_agent_developer, app_developer +:learning-objective-1: Describe the A2A protocol and agent card format +:learning-objective-2: Explain authentication flow for external applications +:learning-objective-3: Identify integration patterns and current limitations + +Understand how external applications and agents integrate with Redpanda Cloud agents using the Agent-to-Agent (A2A) protocol. + +After reading this page, you will be able to: + +* [ ] {learning-objective-1} +* [ ] {learning-objective-2} +* [ ] {learning-objective-3} + +This page covers external agent integration where Redpanda Cloud agents connect to agents hosted elsewhere using the A2A protocol. For internal integration within Redpanda Cloud (agents invoking MCP tools, or pipelines calling agents), see xref:ai-agents:agents/integration-patterns.adoc[]. + +== What is the A2A protocol? + +The Agent-to-Agent (A2A) protocol is an open standard for agent communication and discovery. Agents that implement A2A expose their capabilities through a standardized agent card, allowing external systems to discover and interact with them programmatically. + +Every Redpanda Cloud agent automatically exposes an agent card at: + +---- +https://{agent-id}.ai-agents.{cluster-id}.{cluster-domain}/.well-known/agent-card.json +---- + +The agent card describes: + +* **Agent capabilities**: What skills and tools the agent provides +* **Input/output formats**: Expected request and response structures +* **Protocol version**: A2A specification version supported +* **Communication modes**: Synchronous, streaming, or both + +For the complete A2A specification, see link:https://a2a.ag/spec[a2a.ag/spec^]. + +== When to use A2A integration + +Use A2A integration when you need: + +* **Programmatic access**: Call agents from applications, scripts, or CI/CD pipelines +* **External agent connections**: Connect agents you host elsewhere to Redpanda Cloud agents +* **Custom interfaces**: Build custom UIs or workflows around agent capabilities +* **Multi-system orchestration**: Coordinate agents with other systems in your architecture + +For testing and development, use the Redpanda Cloud UI Inspector tab instead. The Inspector automatically handles authentication and provides a testing interface. + +== Integration patterns + +=== Application-to-agent + +Applications send requests to your agent's HTTP endpoint using any A2A-compatible SDK: + +---- +Application → HTTP/A2A → Redpanda Cloud Agent +---- + +Use this pattern for: + +* Backend services calling agents as part of business logic +* CLI tools that need agent capabilities +* Batch processing systems +* API gateways routing requests to agents + +=== External agent-to-agent + +Connect agents you host elsewhere to Redpanda Cloud agents: + +---- +Your Agent → A2A Protocol → Redpanda Cloud Agent +---- + +Use this pattern for: + +* Agent workflows spanning multiple platforms +* Specialized agents you develop and host separately +* Integration with existing agent infrastructure + +== Authentication flow + +External applications authenticate using OAuth2 client credentials flow with the agent's service account. + +=== How authentication works + +When you create an agent, Redpanda Cloud automatically creates a service account with a client ID and secret. External applications use these credentials to obtain access tokens: + +. **Service account creation**: Agent creation automatically provisions a service account with credentials +. **Token exchange**: Applications exchange the client ID and secret for a time-limited access token via OAuth2 +. **Authenticated requests**: Applications include the access token in the Authorization header when calling the agent endpoint +. **Token refresh**: When tokens expire, applications exchange credentials again for a new token + +This flow ensures: + +* **Credentials are never sent directly to the agent**: Only access tokens are used for agent calls +* **Limited token lifetime**: Tokens expire, reducing the window for compromised credentials +* **Standard OAuth2 protocol**: Applications can use existing OAuth2 libraries + +For step-by-step instructions on exchanging credentials for access tokens and making authenticated requests, see xref:security:cloud-authentication.adoc[Cloud API Authentication]. + +The same OAuth2 client credentials flow used for Redpanda Cloud APIs applies to agent authentication. + +== Security considerations + +=== Protect service account credentials + +* Store client ID and secret in secure credential stores (not in code) +* Use environment variables or secrets management systems +* Rotate credentials if compromised +* Restrict access to credentials based on principle of least privilege + +=== Token scope + +Access tokens grant full access to the agent. Anyone with a valid token can: + +* Send requests to the agent +* Receive responses from the agent +* Consume agent resources (subject to rate limits) + +Treat access tokens like passwords. Do not log them or expose them in error messages. + +== Next steps + +* xref:ai-agents:agents/create-agent.adoc[]: Create an agent with service account credentials +* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems with internal subagents +* link:https://a2a.ag/spec[A2A Protocol Specification^]: Complete protocol reference diff --git a/modules/ai-agents/pages/agents/architecture-patterns.adoc b/modules/ai-agents/pages/agents/architecture-patterns.adoc index aeed99dde..dbfbd3dae 100644 --- a/modules/ai-agents/pages/agents/architecture-patterns.adoc +++ b/modules/ai-agents/pages/agents/architecture-patterns.adoc @@ -166,7 +166,7 @@ Design workflows to complete in 20-30 iterations. Return paginated results from == Model selection guide -Choose models based on task complexity, latency requirements, and cost constraints. The Redpanda Cloud Console displays available models with descriptions when creating agents. +Choose models based on task complexity, latency requirements, and cost constraints. The Redpanda Cloud UI displays available models with descriptions when creating agents. === Match models to task complexity diff --git a/modules/ai-agents/pages/agents/create-agent.adoc b/modules/ai-agents/pages/agents/create-agent.adoc index db4d1883f..5103fb3ce 100644 --- a/modules/ai-agents/pages/agents/create-agent.adoc +++ b/modules/ai-agents/pages/agents/create-agent.adoc @@ -6,7 +6,7 @@ :learning-objective-2: Connect MCP servers and select tools for your agent :learning-objective-3: Set agent execution parameters including max iterations -Create a new AI agent through the Redpanda Cloud Console. This guide walks you through configuring the agent's model, system prompt, tools, and execution settings. +Create a new AI agent through the Redpanda Cloud UI. This guide walks you through configuring the agent's model, system prompt, tools, and execution settings. After reading this page, you will be able to: @@ -16,14 +16,14 @@ After reading this page, you will be able to: == Prerequisites -* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] with Remote MCP enabled. +* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] on AWS running Redpanda version 25.3 and later. * At least one xref:ai-agents:mcp/remote/overview.adoc[Remote MCP server] deployed with tools. * LLM provider API key (OpenAI, Google, or Anthropic). * System prompt prepared (see xref:ai-agents:agents/prompt-best-practices.adoc[System Prompt Best Practices]). == Access the agents UI -. Log in to the link:https://cloud.redpanda.com[Redpanda Cloud Console^]. +. Log in to the link:https://cloud.redpanda.com[Redpanda Cloud UI^]. . Navigate to your cluster. . Click *Agentic AI* > *AI Agents* in the left navigation. @@ -233,7 +233,7 @@ https://.ai-agents.. You can use this URL to call your agent programmatically or integrate it with external systems. -The *Inspector* tab in the Cloud Console automatically uses this URL to connect to your agent for testing. +The *Inspector* tab in the Cloud UI automatically uses this URL to connect to your agent for testing. For programmatic access or external agent integration, see xref:ai-agents:agents/integration-overview.adoc[]. diff --git a/modules/ai-agents/pages/agents/external-app-integration.adoc b/modules/ai-agents/pages/agents/external-app-integration.adoc new file mode 100644 index 000000000..92de59f48 --- /dev/null +++ b/modules/ai-agents/pages/agents/external-app-integration.adoc @@ -0,0 +1,101 @@ += External Application Integration Patterns +:description: Integrate external applications and agents with Redpanda Cloud agents using the A2A protocol. +:page-topic-type: best-practices +:personas: ai_agent_developer, app_developer +:learning-objective-1: Choose between application-to-agent and external agent-to-agent patterns +:learning-objective-2: Protect service account credentials using secure storage +:learning-objective-3: Apply OAuth2 authentication flow for external requests + +Integrate external applications and agents with Redpanda Cloud agents using secure, standardized patterns. + +After reading this page, you will be able to: + +* [ ] {learning-objective-1} +* [ ] {learning-objective-2} +* [ ] {learning-objective-3} + +This page covers external integration where applications or agents hosted outside Redpanda Cloud call Redpanda Cloud agents using the A2A protocol. For internal integration within Redpanda Cloud (agents invoking MCP tools, or pipelines calling agents), see xref:ai-agents:agents/integration-overview.adoc[]. + +For A2A protocol concepts, agent cards, and communication modes, see xref:ai-agents:agents/a2a-concepts.adoc[]. + +== When to use A2A integration + +Use A2A integration when you need: + +* Programmatic access - Call agents from applications, scripts, or CI/CD pipelines. +* External agent connections - Connect agents you host elsewhere to Redpanda Cloud agents. +* Custom interfaces - Build custom UIs or workflows around agent capabilities. +* Multi-system orchestration - Coordinate agents with other systems in your architecture. + +For testing and development, use the Redpanda Cloud UI Inspector tab instead. The Inspector automatically handles authentication and provides a testing interface. + +== Integration patterns + +=== Application-to-agent + +Applications send requests to your agent's HTTP endpoint using any A2A-compatible SDK: + +---- +Application -- HTTP/A2A -- Redpanda Cloud Agent +---- + +Use this pattern for: + +* Backend services calling agents as part of business logic. +* CLI tools that need agent capabilities. +* Batch processing systems. +* API gateways routing requests to agents. + +=== External agent-to-agent + +Connect agents you host elsewhere to Redpanda Cloud agents: + +---- +Your Agent -- A2A Protocol -- Redpanda Cloud Agent +---- + +Use this pattern for: + +* Agent workflows spanning multiple platforms. +* Specialized agents you develop and host separately. +* Integration with existing agent infrastructure. + +== Authentication + +External applications authenticate using OAuth2 client credentials flow with the agent's service account: + +. Exchange service account credentials for an access token +. Include the access token in the Authorization header +. Send requests to the agent's A2A endpoint +. Refresh tokens when they expire + +For implementation details: + +* xref:ai-agents:agents/a2a-concepts.adoc#authentication[]: Learn how A2A authentication works +* xref:security:cloud-authentication.adoc[]: Get step-by-step authentication instructions + +== Security considerations + +=== Protect service account credentials + +* Store the client ID and secret in secure credential stores, not in code. +* Use environment variables or secrets management systems. +* Rotate credentials if compromised. +* Restrict access to credentials based on principle of least privilege. + +=== Protect access tokens + +Access tokens grant full access to the agent. Anyone with a valid token can: + +* Send requests to the agent. +* Receive responses from the agent. +* Consume agent resources (subject to rate limits). + +Treat access tokens like passwords. Never log tokens or include them in error messages. + +== Next steps + +* xref:ai-agents:agents/a2a-concepts.adoc[]: Learn A2A protocol concepts and agent cards +* xref:ai-agents:agents/create-agent.adoc[]: Create an agent with service account credentials +* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems with internal subagents +* link:https://a2a.ag/spec[A2A Protocol Specification^]: Complete protocol reference diff --git a/modules/ai-agents/pages/agents/external-integration.adoc b/modules/ai-agents/pages/agents/external-integration.adoc new file mode 100644 index 000000000..5f457846a --- /dev/null +++ b/modules/ai-agents/pages/agents/external-integration.adoc @@ -0,0 +1,94 @@ += External Agent Integration +:description: Integrate Redpanda Cloud agents with external systems using the A2A protocol. +:page-aliases: agent-to-agent-integration.adoc +:page-topic-type: best-practices +:personas: ai_agent_developer, app_developer +:learning-objective-1: Choose the appropriate external integration pattern for your use case +:learning-objective-2: Apply security best practices for service account credentials +:learning-objective-3: Identify when to use external integration versus internal patterns + +Integrate external applications and agents with Redpanda Cloud agents using secure, standardized patterns. + +After reading this page, you will be able to: + +* [ ] {learning-objective-1} +* [ ] {learning-objective-2} +* [ ] {learning-objective-3} + +This page covers external agent integration where Redpanda Cloud agents connect to agents hosted elsewhere using the A2A protocol. For internal integration within Redpanda Cloud (agents invoking MCP tools, or pipelines calling agents), see xref:ai-agents:agents/internal-integration.adoc[]. + +For A2A protocol concepts, agent cards, and communication modes, see xref:ai-agents:agents/a2a-concepts.adoc[]. + +== When to use A2A integration + +Use A2A integration when you need: + +* Programmatic access - Call agents from applications, scripts, or CI/CD pipelines. +* External agent connections - Connect agents you host elsewhere to Redpanda Cloud agents. +* Custom interfaces - Build custom UIs or workflows around agent capabilities. +* Multi-system orchestration - Coordinate agents with other systems in your architecture. + +For testing and development, use the Redpanda Cloud UI Inspector tab instead. The Inspector automatically handles authentication and provides a testing interface. + +== Integration patterns + +=== Application-to-agent + +Applications send requests to your agent's HTTP endpoint using any A2A-compatible SDK: + +---- +Application → HTTP/A2A → Redpanda Cloud Agent +---- + +Use this pattern for: + +* Backend services calling agents as part of business logic. +* CLI tools that need agent capabilities. +* Batch processing systems. +* API gateways routing requests to agents. + +=== External agent-to-agent + +Connect agents you host elsewhere to Redpanda Cloud agents: + +---- +Your Agent → A2A Protocol → Redpanda Cloud Agent +---- + +Use this pattern for: + +* Agent workflows spanning multiple platforms. +* Specialized agents you develop and host separately. +* Integration with existing agent infrastructure. + +== Authentication + +External applications authenticate using OAuth2 client credentials flow with the agent's service account. For details on how authentication works, see xref:ai-agents:agents/a2a-concepts.adoc#authentication[A2A Protocol Authentication]. + +For step-by-step instructions on exchanging credentials for access tokens and making authenticated requests, see xref:security:cloud-authentication.adoc[]. + +== Security considerations + +=== Protect service account credentials + +* Store client ID and secret in secure credential stores (not in code). +* Use environment variables or secrets management systems. +* Rotate credentials if compromised. +* Restrict access to credentials based on principle of least privilege. + +=== Token scope + +Access tokens grant full access to the agent. Anyone with a valid token can: + +* Send requests to the agent. +* Receive responses from the agent. +* Consume agent resources (subject to rate limits). + +Treat access tokens like passwords. Do not log them or expose them in error messages. + +== Next steps + +* xref:ai-agents:agents/a2a-concepts.adoc[]: Learn A2A protocol concepts and agent cards +* xref:ai-agents:agents/create-agent.adoc[]: Create an agent with service account credentials +* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems with internal subagents +* link:https://a2a.ag/spec[A2A Protocol Specification^]: Complete protocol reference diff --git a/modules/ai-agents/pages/agents/monitor-agents.adoc b/modules/ai-agents/pages/agents/monitor-agents.adoc index c493aa291..71578c7f6 100644 --- a/modules/ai-agents/pages/agents/monitor-agents.adoc +++ b/modules/ai-agents/pages/agents/monitor-agents.adoc @@ -79,7 +79,7 @@ The *Inspector* tab provides real-time conversation testing. Use it to test agen === Access Inspector -. Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud Console. +. Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud UI. . Click your agent name. . Open the *Inspector* tab. . Enter test queries and review responses. diff --git a/modules/ai-agents/pages/agents/quickstart.adoc b/modules/ai-agents/pages/agents/quickstart.adoc index 8e25a0c02..dcfe2b0cf 100644 --- a/modules/ai-agents/pages/agents/quickstart.adoc +++ b/modules/ai-agents/pages/agents/quickstart.adoc @@ -41,7 +41,7 @@ The agent orchestrates the `generate_input` and `redpanda_output` tools you crea == Create the agent -. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud Console^]. +. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud UI^]. . Navigate to your cluster and click *Agentic AI* > *AI Agents* in the left navigation. diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index 7e188f652..edf0edeae 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -36,7 +36,7 @@ The challenge: users phrase requests differently ("Where's my package?", "Track == Prerequisites -* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] with Remote MCP enabled. +* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] on AWS running Redpanda version 25.3 and later. * LLM provider API key (this tutorial uses OpenAI). == Design the MCP tools @@ -61,9 +61,9 @@ This granularity enables the agent to chain tools (check order status, see it's Create a Remote MCP server with the three tools. -. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud Console^] -. Go to *Agentic AI* > *Remote MCP* -. Click *Create MCP Server* +. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud UI^]. +. Go to *Agentic AI* > *Remote MCP*. +. Click *Create MCP Server*. . Configure the server: + * *Name*: `customer-support-tools` @@ -113,8 +113,8 @@ The system prompt teaches the agent how to orchestrate tools. Without explicit g Create the customer support agent with the system prompt. -. Go to *Agentic AI* > *AI Agents* -. Click *Create Agent* +. Go to *Agentic AI* > *AI Agents*. +. Click *Create Agent*. . Configure the agent: + * *Name*: `customer-support-agent` @@ -179,7 +179,7 @@ Wait for the agent status to show *Running*. == Observe orchestration in action -Open the *Inspector* tab in the Redpanda Cloud Console to interact with the agent. +Open the *Inspector* tab in the Redpanda Cloud UI to interact with the agent. Testing reveals how the agent makes decisions. Watch the conversation panel in the built-in chat interface to see the agent's reasoning process unfold. diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index 0e6e08ce9..4d3b2b955 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -32,7 +32,7 @@ When a customer calls saying "I see a $247.83 charge from 'ACME CORP' but I neve == Prerequisites -* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] with Remote MCP enabled. +* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] on AWS running Redpanda version 25.3 and later. * LLM provider API key (this tutorial uses OpenAI GPT-5.2 or Claude Sonnet 4.5 for reasoning). * The xref:get-started:rpk-install.adoc[Redpanda CLI (`rpk`)] installed (for testing the pipeline with sample data). * Completed xref:ai-agents:agents/tutorials/customer-support-agent.adoc[] (foundational multi-tool concepts). @@ -45,7 +45,7 @@ Before creating agents, create the tools they'll use. You'll organize tools by d Account tools retrieve customer and transaction data with PII protection. -. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud Console^]. +. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud UI^]. . Go to *Agentic AI* > *Remote MCP*. . Click *Create MCP Server*. . Configure the server: @@ -441,7 +441,7 @@ https://abc123.ai-agents.def456.cloud.redpanda.com/.well-known/agent-card.json Create the topics the pipeline will use for input and output. -. Go to *Topics* in the Redpanda Cloud Console. +. Go to *Topics* in the Redpanda Cloud UI. . Click *Create Topic*. . Create the input topic: + @@ -460,7 +460,7 @@ Create the topics the pipeline will use for input and output. The pipeline needs SASL credentials to read from and write to Redpanda topics. -. Go to *Security* > *Users* in the Redpanda Cloud Console. +. Go to *Security* > *Users* in the Redpanda Cloud UI. . Click *Create User*. . Configure the user: + @@ -496,7 +496,7 @@ The pipeline needs SASL credentials to read from and write to Redpanda topics. The pipeline needs SASL credentials stored as secrets to authenticate with Redpanda topics. -. Go to *Connect* > *Secrets* in the Redpanda Cloud Console (if not already there). +. Go to *Connect* > *Secrets* in the Redpanda Cloud UI (if not already there). . Click *Create Secret*. . Create two secrets with these values: + @@ -505,7 +505,7 @@ The pipeline needs SASL credentials stored as secrets to authenticate with Redpa === Create the pipeline -. Go to *Connect* in the Redpanda Cloud Console. +. Go to *Connect* in the Redpanda Cloud UI. . Click *Create Pipeline*. . In the numbered steps, click *4 Add permissions*. . Select *Service Account*. diff --git a/modules/ai-agents/pages/mcp/remote/create-tool.adoc b/modules/ai-agents/pages/mcp/remote/create-tool.adoc index 3b13dcec4..1d60444bf 100644 --- a/modules/ai-agents/pages/mcp/remote/create-tool.adoc +++ b/modules/ai-agents/pages/mcp/remote/create-tool.adoc @@ -24,14 +24,14 @@ After reading this page, you will be able to: == Create the tool -In Redpanda Cloud, you create tools directly in the Cloud Console or using the Data Plane API. +In Redpanda Cloud, you create tools directly in the Cloud UI or using the Data Plane API. [tabs] ====== -Cloud Console:: +Cloud UI:: + -- -. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud Console^]. +. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud UI^]. . Navigate to *Agentic AI* > *Remote MCP* and either create a new MCP server or edit an existing one. @@ -207,7 +207,7 @@ Reference secrets using `${secrets.SECRET_NAME}` syntax: include::ai-agents:example$mcp-tools/snippets/secrets.yaml[tag=example,indent=0] ---- -When you add secret references to your tool configuration, the Cloud Console automatically detects them and provides an interface to create the required secrets. +When you add secret references to your tool configuration, the Cloud UI automatically detects them and provides an interface to create the required secrets. === Secrets best practices diff --git a/modules/ai-agents/pages/mcp/remote/manage-servers.adoc b/modules/ai-agents/pages/mcp/remote/manage-servers.adoc index 40fe836f7..61d7e66e7 100644 --- a/modules/ai-agents/pages/mcp/remote/manage-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/manage-servers.adoc @@ -27,10 +27,10 @@ You can update the configuration, resources, or metadata of an MCP server at any [tabs] ===== -Cloud Console:: +Cloud UI:: + -- -. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. . Find the MCP server you want to edit and click its name. . Click *Edit configuration*. . Make your changes. @@ -70,10 +70,10 @@ Stopping a server pauses all tool execution and releases compute resources, but [tabs] ===== -Cloud Console:: +Cloud UI:: + -- -. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. . Find the server you want to stop. . Click the three dots and select *Stop*. . Confirm the action. @@ -101,10 +101,10 @@ Resume a stopped server to restore its functionality. [tabs] ===== -Cloud Console:: +Cloud UI:: + -- -. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. . Find the stopped server. . Click the three dots and select *Start*. . Wait for the status to show *Running* before reconnecting clients. @@ -132,10 +132,10 @@ Deleting a server permanently removes it. You cannot undo this action. Redpanda [tabs] ===== -Cloud Console:: +Cloud UI:: + -- -. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. . Find the server you want to delete. . Click the three dots and select *Delete*. . Confirm the deletion when prompted. diff --git a/modules/ai-agents/pages/mcp/remote/monitor-activity.adoc b/modules/ai-agents/pages/mcp/remote/monitor-activity.adoc new file mode 100644 index 000000000..bd6076560 --- /dev/null +++ b/modules/ai-agents/pages/mcp/remote/monitor-activity.adoc @@ -0,0 +1,113 @@ += Monitor MCP Server Activity +:description: How to consume traces, track tool invocations, measure performance, and debug failures in MCP servers. +:page-topic-type: how-to +:personas: platform_admin, ai_agent_developer, data_engineer +// Reader journey: "I need to accomplish X" +// Learning objectives - what readers can DO with this guide: +:learning-objective-1: Consume traces from transcripts +:learning-objective-2: Track tool invocations and measure performance +:learning-objective-3: Debug tool failures using trace data + +After creating an MCP server, you can monitor its activity using transcripts. + +After reading this page, you will be able to: + +* [ ] {learning-objective-1} +* [ ] {learning-objective-2} +* [ ] {learning-objective-3} + +For conceptual background on traces, spans, and the trace data structure, see xref:ai-agents:mcp/remote/concepts.adoc#transcripts[Transcripts and observability]. + +== Prerequisites + +You must have an existing MCP server. If you do not have one, see xref:ai-agents:mcp/remote/quickstart.adoc[]. + +== Consume traces from transcripts + +MCP servers emit OpenTelemetry traces to the `redpanda.otel_traces` topic. You can consume these traces using any Kafka-compatible client or the Redpanda Cloud UI. + +[tabs] +===== +Cloud UI:: ++ +-- +. In the Redpanda Cloud UI, navigate to *Topics*. +. Select `redpanda.otel_traces`. +. Click *Messages* to view recent traces. +. Use filters to search for specific trace IDs, span names, or time ranges. +-- + +rpk:: ++ +-- +Consume the most recent traces: + +[,bash] +---- +rpk topic consume redpanda.otel_traces --offset end -n 10 +---- + +Filter for specific MCP server activity by examining the span attributes. +-- + +Data Plane API:: ++ +-- +Use the link:/api/doc/cloud-dataplane/[Data Plane API] to programmatically consume traces and integrate with your monitoring pipeline. +-- +===== + +== Track tool invocations + +Monitor which tools are being called and how often: + +. Consume traces from `redpanda.otel_traces`. +. Filter spans where `instrumentationScope.name` is `rpcn-mcp`. +. Examine the `name` field to see which tools are being invoked. +. Calculate frequency by counting spans per tool name over time windows. + +Example: To find all invocations of a specific tool, filter for spans where `name` matches your tool name (for example, `weather`, `http_processor`). + +== Measure performance + +Analyze tool execution times: + +. Find spans with `instrumentationScope.name` set to `rpcn-mcp`. +. Calculate duration: `(endTimeUnixNano - startTimeUnixNano) / 1000000` (milliseconds). +. Track percentiles (p50, p95, p99) to identify performance issues. +. Set alerts for durations exceeding acceptable thresholds. + +Example: A span with `startTimeUnixNano: "1765198415253280028"` and `endTimeUnixNano: "1765198424660663434"` has a duration of 9407ms. + +== Debug failures + +Investigate errors and failures: + +. Filter spans where `status.code` is `2` (error). +. Examine `status.message` for error details. +. Check the `events` array for error events with timestamps. +. Use `traceId` to correlate related spans and understand the full error context. +. Follow `parentSpanId` relationships to trace the error back to the originating tool. + +Example: A span with `status.code: 2` and `status.message: "connection timeout"` indicates the operation failed due to a timeout. + +== Correlate distributed operations + +Link MCP server activity to downstream effects: + +. Extract `traceId` from tool invocation spans. +. Search for the same `traceId` in other application logs or traces. +. Follow `parentSpanId` relationships to build complete operation timelines. +. Identify bottlenecks across your entire system. + +== Integrate with observability platforms + +The `redpanda.otel_traces` topic stores trace data in OpenTelemetry format. Redpanda does not support direct export to platforms like Grafana Cloud and Datadog due to format compatibility limitations. Redpanda produces one span per topic message, whereas these platforms expect traces in batch format. + +You can consume traces directly from the `redpanda.otel_traces` topic using any Kafka-compatible consumer for custom analysis and processing. + +== Next steps + +* xref:ai-agents:mcp/remote/concepts.adoc#transcripts[Transcripts]: Learn how traces and spans work +* xref:ai-agents:mcp/remote/troubleshooting.adoc[]: Diagnose and fix common issues +* xref:ai-agents:mcp/remote/manage-servers.adoc[]: Manage MCP server lifecycle diff --git a/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc index 7966fc3ae..fbb331875 100644 --- a/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc @@ -20,7 +20,7 @@ For conceptual background on traces, spans, and the trace data structure, see xr You must have an existing MCP server. If you do not have one, see xref:ai-agents:mcp/remote/quickstart.adoc[]. -== View transcripts in the Cloud Console +== View transcripts in the Cloud UI :context: mcp include::ai-agents:partial$transcripts-ui-guide.adoc[] @@ -33,10 +33,10 @@ MCP servers emit OpenTelemetry traces to the `redpanda.otel_traces` topic. Consu [tabs] ===== -Cloud Console:: +Cloud UI:: + -- -. In the Redpanda Cloud Console, navigate to *Topics*. +. In the Redpanda Cloud UI, navigate to *Topics*. . Select `redpanda.otel_traces`. . Click *Messages* to view recent traces. . Use filters to search for specific trace IDs, span names, or time ranges. diff --git a/modules/ai-agents/pages/mcp/remote/quickstart.adoc b/modules/ai-agents/pages/mcp/remote/quickstart.adoc index 92ee94aeb..32a41b5aa 100644 --- a/modules/ai-agents/pages/mcp/remote/quickstart.adoc +++ b/modules/ai-agents/pages/mcp/remote/quickstart.adoc @@ -176,10 +176,10 @@ curl -X POST "https:///v1/acls" \ [tabs] ===== -Cloud Console:: +Cloud UI:: + -- -. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud Console^]. +. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud UI^]. . Navigate to *Agentic AI* > *Remote MCP*. + @@ -187,7 +187,7 @@ This page shows a list of existing servers. . Click *Create new MCP Server*. In *Server Metadata*, configure the basic information and resources: + -* *Display Name*: A human-friendly name such as `event-data-generator`. This name is shown in the Redpanda Cloud Console. It is not the name of the MCP server itself. +* *Display Name*: A human-friendly name such as `event-data-generator`. This name is shown in the Redpanda Cloud UI. It is not the name of the MCP server itself. * *Description*: Explain what the server does. For example, `Generates fake user event data and publishes it to Redpanda topics`. * *Tags*: Add key/value tags such as `owner=platform` or `env=demo`. The tag names `service_account_id` and `secret_id` are reserved and cannot be used. * *Resources*: Choose a size (XSmall / Small / Medium / Large / XLarge). Larger sizes allow more concurrent requests and faster processing, but cost more. You can change this later. diff --git a/modules/ai-agents/pages/mcp/remote/scale-resources.adoc b/modules/ai-agents/pages/mcp/remote/scale-resources.adoc index 3c4d948b7..424094577 100644 --- a/modules/ai-agents/pages/mcp/remote/scale-resources.adoc +++ b/modules/ai-agents/pages/mcp/remote/scale-resources.adoc @@ -24,10 +24,10 @@ You must have an existing MCP server. If you do not have one, see xref:ai-agents [tabs] ===== -Cloud Console:: +Cloud UI:: + -- -. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. . Find the MCP server you want to scale and click its name. . Click *Edit configuration*. . Under *Resources*, select a new size: diff --git a/modules/ai-agents/pages/observability/monitor-agents.adoc b/modules/ai-agents/pages/observability/monitor-agents.adoc new file mode 100644 index 000000000..f72affafa --- /dev/null +++ b/modules/ai-agents/pages/observability/monitor-agents.adoc @@ -0,0 +1,226 @@ += Monitor Agent Activity +:description: Monitor agent execution, analyze conversation history, track token usage, and debug issues using inspector, transcripts, and agent data topics. +:page-topic-type: how-to +:personas: ai_agent_developer, platform_admin +:learning-objective-1: pass:q[Test agents interactively using the *Inspector* tab] +:learning-objective-2: Consume session and task topics for analysis +:learning-objective-3: Debug agent behavior using Transcripts + +Monitor your agents to track performance, analyze conversations, debug issues, and optimize costs. + +After reading this page, you will be able to: + +* [ ] {learning-objective-1} +* [ ] {learning-objective-2} +* [ ] {learning-objective-3} + +For conceptual background on traces and observability, see xref:ai-agents:observability/concepts.adoc[]. + +== Prerequisites + +You must have a running agent. If you do not have one, see xref:ai-agents:agents/quickstart.adoc[]. + +== Test agents interactively + +The *Inspector* tab provides real-time conversation testing and debugging. + +You can use the Inspector to: + +* Test agent responses interactively +* View full conversation history +* See tool invocations and results +* Monitor token usage per request +* Test error scenarios + +=== Access the inspector + +. Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud UI. +. Click your agent name. +. Open the *Inspector* tab. +. Enter test queries and review responses. +. Check the conversation panel to see tool calls. +. Start a new session to test fresh conversations. + +=== Testing best practices + +Test your agents systematically: + +* **Boundary cases**: Test requests at the edge of agent capabilities to verify scope enforcement +* **Error handling**: Request unavailable data to verify graceful degradation +* **Iteration count**: Monitor how many iterations complex requests require +* **Ambiguous input**: Send vague queries to verify clarification behavior +* **Token usage**: Track tokens per request to estimate costs + +== View execution traces + +The *Transcripts* view shows agent execution traces with detailed timing and error information. + +=== What Transcripts show + +* Agent startup and initialization +* Request and response flow +* Tool invocations and results +* Error messages and failures +* Token usage per request +* OpenTelemetry trace data + +=== Access Transcripts + +. Navigate to *Agentic AI* > *AI Agents*. +. Click your agent name. +. Click *Transcripts* in the left navigation. +. Select a transcript to view execution details. + +=== When to use Transcripts + +* Agent won't start (deployment issues) +* Tool execution errors +* Unexpected agent behavior +* Debugging conversation flow +* Verifying tool selection logic + +== Consume agent data topics + +Agents emit structured data to two Redpanda topics for monitoring and analysis. + +=== Sessions topic + +The sessions topic (`redpanda.aiagent..sessions`) contains all conversation messages. + +Use the sessions topic to: + +* Review past conversations +* Analyze conversation patterns +* Debug multi-turn interactions +* Understand context management + +=== Tasks topic + +The tasks topic (`redpanda.aiagent..tasks`) contains task execution records with status and artifacts. + +Use the tasks topic to: + +* Monitor task completion rates +* Track token usage and costs +* Debug failed tasks +* Analyze agent performance + +=== Access agent topics + +[tabs] +===== +Cloud UI:: ++ +-- +. Navigate to *Topics* in the Redpanda Cloud UI. +. Find `redpanda.aiagent..sessions` or `redpanda.aiagent..tasks`. +. Click *Messages* to view recent data. +. Use filters to search for specific sessions or task states. +-- + +rpk:: ++ +-- +Consume recent sessions: + +[,bash] +---- +rpk topic consume redpanda.aiagent..sessions --offset end -n 10 +---- + +Consume recent tasks: + +[,bash] +---- +rpk topic consume redpanda.aiagent..tasks --offset end -n 10 +---- +-- + +Data Plane API:: ++ +-- +Use the link:/api/doc/cloud-dataplane/[Data Plane API] to programmatically consume agent data and integrate with your monitoring pipeline. +-- +===== + +For schema details, see xref:ai-agents:agents/concepts.adoc#agent-data-topics[]. + +== Analyze conversation history + +Use conversation history to identify behavior patterns and diagnose issues. + +=== Common patterns + +Look for these indicators when analyzing conversations: + +* Agent calling the same tool repeatedly indicates loop detection is needed +* Large gaps between messages suggest tool timeout or slow execution +* Agent responses without tool calls indicate a tool selection issue +* Fabricated information suggests a missing "never make up data" constraint +* Truncated early messages indicate the context window was exceeded + +=== Analysis workflow + +. Use the inspector to reproduce the issue. +. Review full conversation including tool invocations. +. Identify where agent behavior diverged from expected. +. Check system prompt for missing guidance. +. Verify tool responses are formatted correctly. + +== Track token usage + +Monitor token consumption to optimize costs and set appropriate iteration limits. + +=== View token usage + +Token usage appears in: + +* **Inspector tab**: Shows tokens per request during interactive testing +* **Tasks topic**: Contains `usage` metadata with input/output/total tokens +* **Transcripts**: Displays input and output token counts for each execution + +=== Calculate costs + +Use token data from the tasks topic to estimate costs: + +[,bash] +---- +Cost per request = (total_tokens × model_price_per_token) +---- + +Track costs over time by: + +. Consuming the tasks topic. +. Extracting `metadata.usage.total_tokens` from each task. +. Multiplying by your model's token price. +. Aggregating by time period. + +For cost optimization strategies, see xref:ai-agents:agents/concepts.adoc#cost-calculation[]. + +== Monitor performance + +Track agent performance metrics to identify bottlenecks. + +=== Key metrics + +Monitor these metrics from agent data topics: + +* **Task completion rate**: Percentage of tasks with `TASK_STATE_COMPLETED` +* **Average iterations**: Mean iterations per task from conversation history +* **Token efficiency**: Tokens consumed per successful task completion +* **Tool invocation frequency**: Which tools are called most often +* **Error rate**: Percentage of tasks with `TASK_STATE_FAILED` + +=== Performance analysis + +. Consume the tasks topic over a time window. +. Calculate completion rate: `completed_tasks / total_tasks`. +. Identify high iteration tasks for prompt optimization. +. Track token usage trends over time. +. Correlate errors with specific tool invocations. + +== Next steps + +* xref:ai-agents:observability/concepts.adoc[]: Understand trace structure and OpenTelemetry spans +* xref:ai-agents:agents/troubleshooting.adoc[]: Diagnose and fix common agent issues +* xref:ai-agents:agents/concepts.adoc[]: Learn about agent execution and cost calculation diff --git a/modules/develop/pages/connect/configuration/resource-management.adoc b/modules/develop/pages/connect/configuration/resource-management.adoc index 802e1d8fb..c48a3e58e 100644 --- a/modules/develop/pages/connect/configuration/resource-management.adoc +++ b/modules/develop/pages/connect/configuration/resource-management.adoc @@ -125,7 +125,7 @@ To view resources already allocated to a data pipeline: [tabs] ===== -Cloud Console:: +Cloud UI:: + -- . Log in to https://cloud.redpanda.com[Redpanda Cloud^]. @@ -152,7 +152,7 @@ To scale the resources for a pipeline: [tabs] ===== -Cloud Console:: +Cloud UI:: + -- . Log in to https://cloud.redpanda.com[Redpanda Cloud^]. diff --git a/modules/develop/pages/connect/configuration/secret-management.adoc b/modules/develop/pages/connect/configuration/secret-management.adoc index 026616216..ac0fee981 100644 --- a/modules/develop/pages/connect/configuration/secret-management.adoc +++ b/modules/develop/pages/connect/configuration/secret-management.adoc @@ -15,7 +15,7 @@ You can create a secret and reference it in multiple data pipelines on the same [tabs] ===== -Cloud Console:: +Cloud UI:: + -- . Log in to https://cloud.redpanda.com[Redpanda Cloud^]. @@ -71,7 +71,7 @@ NOTE: Changes to secret values do not take effect until a pipeline is restarted. [tabs] ===== -Cloud Console:: +Cloud UI:: + -- . Log in to https://cloud.redpanda.com[Redpanda Cloud^]. @@ -122,7 +122,7 @@ NOTE: Changes do not affect pipelines that are already running. [tabs] ===== -Cloud Console:: +Cloud UI:: + -- . Log in to https://cloud.redpanda.com[Redpanda Cloud^]. @@ -158,7 +158,7 @@ You must include the following values: [tabs] ===== -Cloud Console:: +Cloud UI:: + -- . Go to the **Connect** page, and create a pipeline (or open an existing pipeline to edit). From 35ce1a3500381a9c7a6a35781dc275457e9fbc39 Mon Sep 17 00:00:00 2001 From: Jake Cahill <45230295+JakeSCahill@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:26:17 +0000 Subject: [PATCH 79/97] Update modules/ai-agents/pages/agents/overview.adoc Co-authored-by: Michele Cyran --- modules/ai-agents/pages/agents/overview.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/agents/overview.adoc b/modules/ai-agents/pages/agents/overview.adoc index 1b8af540b..6d9541b2e 100644 --- a/modules/ai-agents/pages/agents/overview.adoc +++ b/modules/ai-agents/pages/agents/overview.adoc @@ -6,7 +6,7 @@ :learning-objective-2: Explain how Redpanda Cloud streaming infrastructure benefits agent architectures :learning-objective-3: Identify use cases where Redpanda Cloud agents provide value -AI agents are systems that combine large language models with the ability to execute actions and process data. Redpanda Cloud provides real-time streaming infrastructure and standardized tool access to support agent development. +AI agents are systems that combine large language models (LLMs) with the ability to execute actions and process data. Redpanda Cloud provides real-time streaming infrastructure and standardized tool access to support agent development. After reading this page, you will be able to: From 72806fe292e94fbe8618bc15322fca3fe344a8e1 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Fri, 30 Jan 2026 17:29:21 +0000 Subject: [PATCH 80/97] Remove alias --- modules/ai-agents/pages/agents/a2a-concepts.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ai-agents/pages/agents/a2a-concepts.adoc b/modules/ai-agents/pages/agents/a2a-concepts.adoc index 45680aa96..48599c2a3 100644 --- a/modules/ai-agents/pages/agents/a2a-concepts.adoc +++ b/modules/ai-agents/pages/agents/a2a-concepts.adoc @@ -1,7 +1,6 @@ = A2A Protocol :description: Learn how the A2A protocol enables agent discovery and communication. :page-topic-type: concepts -:page-aliases: agents/external-app-integration.adoc :personas: agent_developer, app_developer, streaming_developer :learning-objective-1: Describe the A2A protocol and its role in agent communication :learning-objective-2: Explain how agent cards enable discovery From 3f5383ef853f6379f1010c70f61fc0836c096185 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Fri, 30 Jan 2026 18:22:51 +0000 Subject: [PATCH 81/97] Remove incorrectly added files from previous commit --- .../ai-agents/examples/test-mcp-examples.sh | 221 ------------- modules/ai-agents/examples/testing.adoc | 290 ------------------ .../agents/agent-to-agent-integration.adoc | 126 -------- .../agents/external-app-integration.adoc | 101 ------ .../pages/agents/external-integration.adoc | 94 ------ .../pages/mcp/remote/monitor-activity.adoc | 113 ------- .../pages/observability/monitor-agents.adoc | 226 -------------- 7 files changed, 1171 deletions(-) delete mode 100644 modules/ai-agents/examples/test-mcp-examples.sh delete mode 100644 modules/ai-agents/examples/testing.adoc delete mode 100644 modules/ai-agents/pages/agents/agent-to-agent-integration.adoc delete mode 100644 modules/ai-agents/pages/agents/external-app-integration.adoc delete mode 100644 modules/ai-agents/pages/agents/external-integration.adoc delete mode 100644 modules/ai-agents/pages/mcp/remote/monitor-activity.adoc delete mode 100644 modules/ai-agents/pages/observability/monitor-agents.adoc diff --git a/modules/ai-agents/examples/test-mcp-examples.sh b/modules/ai-agents/examples/test-mcp-examples.sh deleted file mode 100644 index 54d6495c0..000000000 --- a/modules/ai-agents/examples/test-mcp-examples.sh +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env bash -# -# Test script for Redpanda Cloud MCP examples -# -# This script tests: -# 1. MCP tool definitions using `rpk connect mcp-server lint` -# 2. MCP metadata validation (enabled, description, properties) -# -# Usage: -# ./test-mcp-examples.sh # Run all tests -# ./test-mcp-examples.sh --lint-only # Only lint, skip metadata validation -# -# Unlike rp-connect-docs, Cloud MCP tools cannot be tested with -# `rpk connect run` because they are standalone tool definitions, not -# full pipelines. End-to-end testing requires the Cloud UI. - -set -euo pipefail - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' - -# Get script directory and set MCP tools directory -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -MCP_TOOLS_DIR="$SCRIPT_DIR/mcp-tools" -cd "$MCP_TOOLS_DIR" - -# Counters -TOTAL_TOOLS=0 -PASSED_LINT=0 -PASSED_METADATA=0 -FAILED_LINT=0 -FAILED_METADATA=0 -SKIPPED=0 - -echo "🧪 Redpanda Cloud MCP Examples - Test Suite" -echo "============================================" -echo "" - -# Parse arguments -RUN_METADATA=true - -if [[ $# -gt 0 ]]; then - case "$1" in - --lint-only) - RUN_METADATA=false - ;; - esac -fi - -# ============================================================================ -# SECTION 1: MCP Tool Linting -# Validates YAML syntax and component schemas -# ============================================================================ - -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo -e "📦 ${CYAN}SECTION 1: MCP Tool Linting${NC}" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "" - -# Count YAML files -file_count=$(find . -maxdepth 1 -name "*.yaml" | wc -l | tr -d ' ') -TOTAL_TOOLS=$file_count - -echo -n -e "${BLUE}📁 mcp-tools/${NC} ($file_count files)... " - -if output=$(rpk connect mcp-server lint --skip-env-var-check . 2>&1); then - echo -e "${GREEN}✓ PASSED${NC}" - PASSED_LINT=$file_count -else - echo -e "${RED}✗ FAILED${NC}" - echo "$output" | sed 's/^/ /' | head -20 - FAILED_LINT=$file_count -fi - -# ============================================================================ -# SECTION 2: MCP Metadata Validation -# Validates tool metadata (enabled, description, properties) -# ============================================================================ - -if $RUN_METADATA; then - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo -e "📝 ${CYAN}SECTION 2: MCP Metadata Validation${NC}" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "" - - # Determine which YAML parser to use - use_yq=true - if ! command -v yq &> /dev/null; then - use_yq=false - if ! command -v python3 &> /dev/null; then - echo -e "${YELLOW}⚠ Neither yq nor python3 available - skipping metadata validation${NC}" - RUN_METADATA=false - fi - fi - - if $RUN_METADATA; then - for file in *.yaml; do - if [[ -f "$file" ]]; then - echo -n -e " ${BLUE}$file${NC}... " - - # Check if .meta.mcp exists - if $use_yq; then - mcp_exists=$(yq eval '.meta.mcp' "$file" 2>/dev/null) - enabled=$(yq eval '.meta.mcp.enabled' "$file" 2>/dev/null) - description=$(yq eval '.meta.mcp.description' "$file" 2>/dev/null) - else - mcp_exists=$(python3 -c " -import yaml -try: - with open('$file') as f: - doc = yaml.safe_load(f) - meta = doc.get('meta', {}) if doc else {} - mcp = meta.get('mcp') - print('null' if mcp is None else 'exists') -except: - print('null') -" 2>/dev/null) - enabled=$(python3 -c " -import yaml -try: - with open('$file') as f: - doc = yaml.safe_load(f) - enabled = doc.get('meta', {}).get('mcp', {}).get('enabled') - print('null' if enabled is None else str(enabled).lower()) -except: - print('null') -" 2>/dev/null) - description=$(python3 -c " -import yaml -try: - with open('$file') as f: - doc = yaml.safe_load(f) - desc = doc.get('meta', {}).get('mcp', {}).get('description') - print('null' if desc is None or desc == '' else str(desc)) -except: - print('null') -" 2>/dev/null) - fi - - # Validate - if [[ "$mcp_exists" == "null" || -z "$mcp_exists" ]]; then - echo -e "${YELLOW}SKIPPED${NC} (no MCP metadata)" - SKIPPED=$((SKIPPED + 1)) - elif [[ "$enabled" != "true" ]]; then - echo -e "${YELLOW}WARNING${NC} (mcp.enabled not true)" - SKIPPED=$((SKIPPED + 1)) - elif [[ "$description" == "null" || -z "$description" ]]; then - echo -e "${RED}FAILED${NC} (missing description)" - FAILED_METADATA=$((FAILED_METADATA + 1)) - else - echo -e "${GREEN}PASSED${NC}" - PASSED_METADATA=$((PASSED_METADATA + 1)) - fi - fi - done - fi -fi - -# ============================================================================ -# SECTION 3: Cloud-Specific Validation -# Validates secrets use Cloud format (${secrets.X}) -# ============================================================================ - -echo "" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo -e "☁️ ${CYAN}SECTION 3: Cloud Secrets Format${NC}" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "" - -secrets_issues=0 -for file in *.yaml; do - if [[ -f "$file" ]]; then - # Check for non-Cloud secrets patterns (${VAR} without secrets. prefix) - # Exclude: - # - ${! ... } which is Bloblang interpolation - # - ${REDPANDA_BROKERS} which is platform-injected - if grep -E '\$\{[A-Z_]+\}' "$file" | grep -v '\${secrets\.' | grep -v '\${!' | grep -v '\${REDPANDA_BROKERS}' > /dev/null 2>&1; then - echo -e " ${BLUE}$file${NC}... ${YELLOW}WARNING${NC} (uses env vars instead of \${secrets.X})" - secrets_issues=$((secrets_issues + 1)) - fi - fi -done - -if [[ $secrets_issues -eq 0 ]]; then - echo -e " ${GREEN}✓ All files use Cloud secrets format${NC}" -fi - -# ============================================================================ -# SUMMARY -# ============================================================================ - -echo "" -echo "============================================" -echo "📊 Test Summary" -echo "============================================" - -echo -e "Lint: ${PASSED_LINT}/${TOTAL_TOOLS} passed" -if $RUN_METADATA; then - METADATA_TOTAL=$((PASSED_METADATA + FAILED_METADATA + SKIPPED)) - echo -e "Metadata: ${PASSED_METADATA}/${METADATA_TOTAL} passed (${SKIPPED} skipped)" -fi -if [[ $secrets_issues -gt 0 ]]; then - echo -e "Secrets: ${YELLOW}${secrets_issues} warnings${NC}" -fi -echo "──────────────────────────────────────────────────" - -TOTAL_FAILED=$((FAILED_LINT + FAILED_METADATA)) - -if [[ $TOTAL_FAILED -gt 0 ]]; then - echo -e "${RED}❌ Some tests failed ($TOTAL_FAILED failures)${NC}" - exit 1 -else - echo -e "${GREEN}✅ All tests passed!${NC}" - exit 0 -fi diff --git a/modules/ai-agents/examples/testing.adoc b/modules/ai-agents/examples/testing.adoc deleted file mode 100644 index 413fa7af2..000000000 --- a/modules/ai-agents/examples/testing.adoc +++ /dev/null @@ -1,290 +0,0 @@ -= Test MCP Examples -:description: Automated testing strategies for Redpanda Cloud MCP server examples. - -This document describes the automated testing strategies for Redpanda Cloud MCP server examples. - -All MCP examples are automatically tested to ensure: - -. YAML syntax and structure are correct -. MCP metadata is complete and valid -. Component schemas match Redpanda Connect specifications -. Secrets syntax uses Cloud Secrets Store format (`${secrets.X}`) - -== Testing approaches - -=== Configuration linting - -Validate MCP tool configurations using `rpk connect lint`: - -[,bash] ----- -# Lint a single MCP tool -rpk connect lint weather_service.yaml - -# Lint all examples -rpk connect lint *.yaml - -# Lint with environment variable checking skipped (recommended for MCP) -rpk connect lint --skip-env-var-check *.yaml ----- - -This checks for common issues such as: - -* YAML syntax errors -* Unknown component types -* Invalid field names -* Type mismatches -* Missing required fields - -=== MCP metadata validation - -The test script validates MCP-specific metadata for all tool examples: - -[,bash] ----- -# Run all tests (includes linting + MCP validation) -./test-mcp-examples.sh - -# Test specific files -./test-mcp-examples.sh weather_*.yaml ----- - -MCP metadata validation checks: - -* Presence of `meta.mcp` section -* `enabled: true` is set -* `description` field exists and is non-empty -* `properties` are properly structured (if present) - -=== Unit testing limitations - -MCP tool examples are standalone component definitions (`label:`, `processors:`, `meta:`), not full pipelines with `input:`, `pipeline:`, `output:` sections. This means they cannot use inline `tests:` sections like cookbook examples do. - -The `rpk connect test` command requires full pipeline structure with paths like `/pipeline/processors/0`, which don't exist in MCP tool definitions. - -For testing MCP tools: - -- Ensure syntax and schema correctness. -- Verify MCP metadata has proper description and properties. -- Perform manual testing using the Cloud UI MCP Server interface to test tools end-to-end. - -== MCP tool structure - -MCP tools are structured as standalone components: - -[,yaml] ----- -label: weather-service -processors: - - label: fetch_weather_data - http: - url: 'https://wttr.in/${! @city }?format=j1' - verb: GET - - - label: format_response - mutation: | - root = { - "city": @city, - "temperature": this.current_condition.0.temp_C.number() - } - -meta: - mcp: - enabled: true - description: "Get current weather conditions for any city worldwide" - properties: - - name: city - type: string - description: "Name of the city" - required: true ----- - -== Test script usage - -The `test-mcp-examples.sh` script provides automated validation: - -[,bash] ----- -# Test all examples -./test-mcp-examples.sh - -# Test specific files -./test-mcp-examples.sh weather_*.yaml -./test-mcp-examples.sh customer_*.yaml ----- - -The script provides color-coded output: - -[,console] ----- -🧪 Redpanda Connect MCP Examples Test Suite (Cloud) -==================================================== - -📄 Testing: weather_service.yaml - Linting weather_service.yaml... PASSED - Validating MCP metadata... PASSED - -==================================================== -📊 Test Summary -==================================================== -Total configs tested: 10 -Passed: 10 -Failed: 0 - -✅ All tests passed! ----- - -== Manual end-to-end testing - -For comprehensive validation, test MCP tools using the Cloud UI: - -. Navigate to your Cloud cluster's MCP Server configuration -. Add or update your MCP tool configuration -. Use the Cloud UI's MCP Inspector to locate your tool -. Verify the tool executes correctly and returns expected results - -This validates: - -* Tool loads correctly in the MCP server -* Tool executes with provided parameters -* Responses are formatted correctly -* Secrets are properly resolved from Cloud Secrets Store - -== GitHub Actions CI/CD - -Automated tests run on every push and pull request using GitHub Actions. - -The workflow tests all examples whenever: - -* Any `.yaml` file in `modules/ai-agents/examples/mcp-tools/` changes -* The test script itself is modified - -See `.github/workflows/test-mcp-examples.yaml` for the complete workflow. - -== Best practices - -=== Use descriptive tool names - -[,yaml] ----- -# Good -label: fetch-customer-orders - -# Bad -label: tool1 ----- - -=== Write clear MCP descriptions - -[,yaml] ----- -# Good -meta: - mcp: - description: "Fetch a customer's order history and calculate spending metrics over the last 30 days" - -# Bad -meta: - mcp: - description: "Get orders" ----- - -=== Document all properties - -[,yaml] ----- -# Good -properties: - - name: customer_id - type: string - description: "Unique identifier for the customer" - required: true - - name: days - type: number - description: "Number of days to look back (default: 30)" - required: false - -# Bad -properties: - - name: id - type: string - required: true ----- - -=== Use Cloud Secrets Store for sensitive data - -[,yaml] ----- -# Cloud format - uses Secrets Store -sql_select: - driver: "postgres" - dsn: "${secrets.POSTGRES_DSN}" - table: "customers" ----- - -=== Tag your examples - -[,yaml] ----- -meta: - tags: [ example, weather, api ] # Helps organize and filter - mcp: - enabled: true ----- - -== Adding new examples - -When adding new MCP tool examples: - -. **Create your YAML file** in `modules/ai-agents/examples/mcp-tools/`: -+ -[,bash] ----- -cd modules/ai-agents/examples -touch my-new-tool.yaml ----- - -. **Include complete MCP metadata:** -+ -[,yaml] ----- -label: my-new-tool -processors: - # Your processor configuration - -meta: - mcp: - enabled: true - description: "Clear, task-oriented description" - properties: - - name: param_name - type: string - description: "Parameter purpose and constraints" - required: true ----- - -. **Lint your example:** -+ -[,bash] ----- -rpk connect lint --skip-env-var-check my-new-tool.yaml ----- - -. **Run automated tests:** -+ -[,bash] ----- -./test-mcp-examples.sh my-new-tool.yaml ----- - -. **Test in Cloud UI (recommended):** -+ -Deploy your MCP server configuration and test the tool through the Cloud UI AI interface. - -. **Commit your example:** -+ -[,bash] ----- -git add modules/ai-agents/examples/mcp-tools/my-new-tool.yaml -git commit -m "Add my-new-tool MCP example" ----- diff --git a/modules/ai-agents/pages/agents/agent-to-agent-integration.adoc b/modules/ai-agents/pages/agents/agent-to-agent-integration.adoc deleted file mode 100644 index 2c7116135..000000000 --- a/modules/ai-agents/pages/agents/agent-to-agent-integration.adoc +++ /dev/null @@ -1,126 +0,0 @@ -= External Agent Integration -:description: Integrate Redpanda Cloud agents with external systems using the A2A protocol. -:page-topic-type: concept -:personas: ai_agent_developer, app_developer -:learning-objective-1: Describe the A2A protocol and agent card format -:learning-objective-2: Explain authentication flow for external applications -:learning-objective-3: Identify integration patterns and current limitations - -Understand how external applications and agents integrate with Redpanda Cloud agents using the Agent-to-Agent (A2A) protocol. - -After reading this page, you will be able to: - -* [ ] {learning-objective-1} -* [ ] {learning-objective-2} -* [ ] {learning-objective-3} - -This page covers external agent integration where Redpanda Cloud agents connect to agents hosted elsewhere using the A2A protocol. For internal integration within Redpanda Cloud (agents invoking MCP tools, or pipelines calling agents), see xref:ai-agents:agents/integration-patterns.adoc[]. - -== What is the A2A protocol? - -The Agent-to-Agent (A2A) protocol is an open standard for agent communication and discovery. Agents that implement A2A expose their capabilities through a standardized agent card, allowing external systems to discover and interact with them programmatically. - -Every Redpanda Cloud agent automatically exposes an agent card at: - ----- -https://{agent-id}.ai-agents.{cluster-id}.{cluster-domain}/.well-known/agent-card.json ----- - -The agent card describes: - -* **Agent capabilities**: What skills and tools the agent provides -* **Input/output formats**: Expected request and response structures -* **Protocol version**: A2A specification version supported -* **Communication modes**: Synchronous, streaming, or both - -For the complete A2A specification, see link:https://a2a.ag/spec[a2a.ag/spec^]. - -== When to use A2A integration - -Use A2A integration when you need: - -* **Programmatic access**: Call agents from applications, scripts, or CI/CD pipelines -* **External agent connections**: Connect agents you host elsewhere to Redpanda Cloud agents -* **Custom interfaces**: Build custom UIs or workflows around agent capabilities -* **Multi-system orchestration**: Coordinate agents with other systems in your architecture - -For testing and development, use the Redpanda Cloud UI Inspector tab instead. The Inspector automatically handles authentication and provides a testing interface. - -== Integration patterns - -=== Application-to-agent - -Applications send requests to your agent's HTTP endpoint using any A2A-compatible SDK: - ----- -Application → HTTP/A2A → Redpanda Cloud Agent ----- - -Use this pattern for: - -* Backend services calling agents as part of business logic -* CLI tools that need agent capabilities -* Batch processing systems -* API gateways routing requests to agents - -=== External agent-to-agent - -Connect agents you host elsewhere to Redpanda Cloud agents: - ----- -Your Agent → A2A Protocol → Redpanda Cloud Agent ----- - -Use this pattern for: - -* Agent workflows spanning multiple platforms -* Specialized agents you develop and host separately -* Integration with existing agent infrastructure - -== Authentication flow - -External applications authenticate using OAuth2 client credentials flow with the agent's service account. - -=== How authentication works - -When you create an agent, Redpanda Cloud automatically creates a service account with a client ID and secret. External applications use these credentials to obtain access tokens: - -. **Service account creation**: Agent creation automatically provisions a service account with credentials -. **Token exchange**: Applications exchange the client ID and secret for a time-limited access token via OAuth2 -. **Authenticated requests**: Applications include the access token in the Authorization header when calling the agent endpoint -. **Token refresh**: When tokens expire, applications exchange credentials again for a new token - -This flow ensures: - -* **Credentials are never sent directly to the agent**: Only access tokens are used for agent calls -* **Limited token lifetime**: Tokens expire, reducing the window for compromised credentials -* **Standard OAuth2 protocol**: Applications can use existing OAuth2 libraries - -For step-by-step instructions on exchanging credentials for access tokens and making authenticated requests, see xref:security:cloud-authentication.adoc[Cloud API Authentication]. - -The same OAuth2 client credentials flow used for Redpanda Cloud APIs applies to agent authentication. - -== Security considerations - -=== Protect service account credentials - -* Store client ID and secret in secure credential stores (not in code) -* Use environment variables or secrets management systems -* Rotate credentials if compromised -* Restrict access to credentials based on principle of least privilege - -=== Token scope - -Access tokens grant full access to the agent. Anyone with a valid token can: - -* Send requests to the agent -* Receive responses from the agent -* Consume agent resources (subject to rate limits) - -Treat access tokens like passwords. Do not log them or expose them in error messages. - -== Next steps - -* xref:ai-agents:agents/create-agent.adoc[]: Create an agent with service account credentials -* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems with internal subagents -* link:https://a2a.ag/spec[A2A Protocol Specification^]: Complete protocol reference diff --git a/modules/ai-agents/pages/agents/external-app-integration.adoc b/modules/ai-agents/pages/agents/external-app-integration.adoc deleted file mode 100644 index 92de59f48..000000000 --- a/modules/ai-agents/pages/agents/external-app-integration.adoc +++ /dev/null @@ -1,101 +0,0 @@ -= External Application Integration Patterns -:description: Integrate external applications and agents with Redpanda Cloud agents using the A2A protocol. -:page-topic-type: best-practices -:personas: ai_agent_developer, app_developer -:learning-objective-1: Choose between application-to-agent and external agent-to-agent patterns -:learning-objective-2: Protect service account credentials using secure storage -:learning-objective-3: Apply OAuth2 authentication flow for external requests - -Integrate external applications and agents with Redpanda Cloud agents using secure, standardized patterns. - -After reading this page, you will be able to: - -* [ ] {learning-objective-1} -* [ ] {learning-objective-2} -* [ ] {learning-objective-3} - -This page covers external integration where applications or agents hosted outside Redpanda Cloud call Redpanda Cloud agents using the A2A protocol. For internal integration within Redpanda Cloud (agents invoking MCP tools, or pipelines calling agents), see xref:ai-agents:agents/integration-overview.adoc[]. - -For A2A protocol concepts, agent cards, and communication modes, see xref:ai-agents:agents/a2a-concepts.adoc[]. - -== When to use A2A integration - -Use A2A integration when you need: - -* Programmatic access - Call agents from applications, scripts, or CI/CD pipelines. -* External agent connections - Connect agents you host elsewhere to Redpanda Cloud agents. -* Custom interfaces - Build custom UIs or workflows around agent capabilities. -* Multi-system orchestration - Coordinate agents with other systems in your architecture. - -For testing and development, use the Redpanda Cloud UI Inspector tab instead. The Inspector automatically handles authentication and provides a testing interface. - -== Integration patterns - -=== Application-to-agent - -Applications send requests to your agent's HTTP endpoint using any A2A-compatible SDK: - ----- -Application -- HTTP/A2A -- Redpanda Cloud Agent ----- - -Use this pattern for: - -* Backend services calling agents as part of business logic. -* CLI tools that need agent capabilities. -* Batch processing systems. -* API gateways routing requests to agents. - -=== External agent-to-agent - -Connect agents you host elsewhere to Redpanda Cloud agents: - ----- -Your Agent -- A2A Protocol -- Redpanda Cloud Agent ----- - -Use this pattern for: - -* Agent workflows spanning multiple platforms. -* Specialized agents you develop and host separately. -* Integration with existing agent infrastructure. - -== Authentication - -External applications authenticate using OAuth2 client credentials flow with the agent's service account: - -. Exchange service account credentials for an access token -. Include the access token in the Authorization header -. Send requests to the agent's A2A endpoint -. Refresh tokens when they expire - -For implementation details: - -* xref:ai-agents:agents/a2a-concepts.adoc#authentication[]: Learn how A2A authentication works -* xref:security:cloud-authentication.adoc[]: Get step-by-step authentication instructions - -== Security considerations - -=== Protect service account credentials - -* Store the client ID and secret in secure credential stores, not in code. -* Use environment variables or secrets management systems. -* Rotate credentials if compromised. -* Restrict access to credentials based on principle of least privilege. - -=== Protect access tokens - -Access tokens grant full access to the agent. Anyone with a valid token can: - -* Send requests to the agent. -* Receive responses from the agent. -* Consume agent resources (subject to rate limits). - -Treat access tokens like passwords. Never log tokens or include them in error messages. - -== Next steps - -* xref:ai-agents:agents/a2a-concepts.adoc[]: Learn A2A protocol concepts and agent cards -* xref:ai-agents:agents/create-agent.adoc[]: Create an agent with service account credentials -* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems with internal subagents -* link:https://a2a.ag/spec[A2A Protocol Specification^]: Complete protocol reference diff --git a/modules/ai-agents/pages/agents/external-integration.adoc b/modules/ai-agents/pages/agents/external-integration.adoc deleted file mode 100644 index 5f457846a..000000000 --- a/modules/ai-agents/pages/agents/external-integration.adoc +++ /dev/null @@ -1,94 +0,0 @@ -= External Agent Integration -:description: Integrate Redpanda Cloud agents with external systems using the A2A protocol. -:page-aliases: agent-to-agent-integration.adoc -:page-topic-type: best-practices -:personas: ai_agent_developer, app_developer -:learning-objective-1: Choose the appropriate external integration pattern for your use case -:learning-objective-2: Apply security best practices for service account credentials -:learning-objective-3: Identify when to use external integration versus internal patterns - -Integrate external applications and agents with Redpanda Cloud agents using secure, standardized patterns. - -After reading this page, you will be able to: - -* [ ] {learning-objective-1} -* [ ] {learning-objective-2} -* [ ] {learning-objective-3} - -This page covers external agent integration where Redpanda Cloud agents connect to agents hosted elsewhere using the A2A protocol. For internal integration within Redpanda Cloud (agents invoking MCP tools, or pipelines calling agents), see xref:ai-agents:agents/internal-integration.adoc[]. - -For A2A protocol concepts, agent cards, and communication modes, see xref:ai-agents:agents/a2a-concepts.adoc[]. - -== When to use A2A integration - -Use A2A integration when you need: - -* Programmatic access - Call agents from applications, scripts, or CI/CD pipelines. -* External agent connections - Connect agents you host elsewhere to Redpanda Cloud agents. -* Custom interfaces - Build custom UIs or workflows around agent capabilities. -* Multi-system orchestration - Coordinate agents with other systems in your architecture. - -For testing and development, use the Redpanda Cloud UI Inspector tab instead. The Inspector automatically handles authentication and provides a testing interface. - -== Integration patterns - -=== Application-to-agent - -Applications send requests to your agent's HTTP endpoint using any A2A-compatible SDK: - ----- -Application → HTTP/A2A → Redpanda Cloud Agent ----- - -Use this pattern for: - -* Backend services calling agents as part of business logic. -* CLI tools that need agent capabilities. -* Batch processing systems. -* API gateways routing requests to agents. - -=== External agent-to-agent - -Connect agents you host elsewhere to Redpanda Cloud agents: - ----- -Your Agent → A2A Protocol → Redpanda Cloud Agent ----- - -Use this pattern for: - -* Agent workflows spanning multiple platforms. -* Specialized agents you develop and host separately. -* Integration with existing agent infrastructure. - -== Authentication - -External applications authenticate using OAuth2 client credentials flow with the agent's service account. For details on how authentication works, see xref:ai-agents:agents/a2a-concepts.adoc#authentication[A2A Protocol Authentication]. - -For step-by-step instructions on exchanging credentials for access tokens and making authenticated requests, see xref:security:cloud-authentication.adoc[]. - -== Security considerations - -=== Protect service account credentials - -* Store client ID and secret in secure credential stores (not in code). -* Use environment variables or secrets management systems. -* Rotate credentials if compromised. -* Restrict access to credentials based on principle of least privilege. - -=== Token scope - -Access tokens grant full access to the agent. Anyone with a valid token can: - -* Send requests to the agent. -* Receive responses from the agent. -* Consume agent resources (subject to rate limits). - -Treat access tokens like passwords. Do not log them or expose them in error messages. - -== Next steps - -* xref:ai-agents:agents/a2a-concepts.adoc[]: Learn A2A protocol concepts and agent cards -* xref:ai-agents:agents/create-agent.adoc[]: Create an agent with service account credentials -* xref:ai-agents:agents/architecture-patterns.adoc[]: Design multi-agent systems with internal subagents -* link:https://a2a.ag/spec[A2A Protocol Specification^]: Complete protocol reference diff --git a/modules/ai-agents/pages/mcp/remote/monitor-activity.adoc b/modules/ai-agents/pages/mcp/remote/monitor-activity.adoc deleted file mode 100644 index bd6076560..000000000 --- a/modules/ai-agents/pages/mcp/remote/monitor-activity.adoc +++ /dev/null @@ -1,113 +0,0 @@ -= Monitor MCP Server Activity -:description: How to consume traces, track tool invocations, measure performance, and debug failures in MCP servers. -:page-topic-type: how-to -:personas: platform_admin, ai_agent_developer, data_engineer -// Reader journey: "I need to accomplish X" -// Learning objectives - what readers can DO with this guide: -:learning-objective-1: Consume traces from transcripts -:learning-objective-2: Track tool invocations and measure performance -:learning-objective-3: Debug tool failures using trace data - -After creating an MCP server, you can monitor its activity using transcripts. - -After reading this page, you will be able to: - -* [ ] {learning-objective-1} -* [ ] {learning-objective-2} -* [ ] {learning-objective-3} - -For conceptual background on traces, spans, and the trace data structure, see xref:ai-agents:mcp/remote/concepts.adoc#transcripts[Transcripts and observability]. - -== Prerequisites - -You must have an existing MCP server. If you do not have one, see xref:ai-agents:mcp/remote/quickstart.adoc[]. - -== Consume traces from transcripts - -MCP servers emit OpenTelemetry traces to the `redpanda.otel_traces` topic. You can consume these traces using any Kafka-compatible client or the Redpanda Cloud UI. - -[tabs] -===== -Cloud UI:: -+ --- -. In the Redpanda Cloud UI, navigate to *Topics*. -. Select `redpanda.otel_traces`. -. Click *Messages* to view recent traces. -. Use filters to search for specific trace IDs, span names, or time ranges. --- - -rpk:: -+ --- -Consume the most recent traces: - -[,bash] ----- -rpk topic consume redpanda.otel_traces --offset end -n 10 ----- - -Filter for specific MCP server activity by examining the span attributes. --- - -Data Plane API:: -+ --- -Use the link:/api/doc/cloud-dataplane/[Data Plane API] to programmatically consume traces and integrate with your monitoring pipeline. --- -===== - -== Track tool invocations - -Monitor which tools are being called and how often: - -. Consume traces from `redpanda.otel_traces`. -. Filter spans where `instrumentationScope.name` is `rpcn-mcp`. -. Examine the `name` field to see which tools are being invoked. -. Calculate frequency by counting spans per tool name over time windows. - -Example: To find all invocations of a specific tool, filter for spans where `name` matches your tool name (for example, `weather`, `http_processor`). - -== Measure performance - -Analyze tool execution times: - -. Find spans with `instrumentationScope.name` set to `rpcn-mcp`. -. Calculate duration: `(endTimeUnixNano - startTimeUnixNano) / 1000000` (milliseconds). -. Track percentiles (p50, p95, p99) to identify performance issues. -. Set alerts for durations exceeding acceptable thresholds. - -Example: A span with `startTimeUnixNano: "1765198415253280028"` and `endTimeUnixNano: "1765198424660663434"` has a duration of 9407ms. - -== Debug failures - -Investigate errors and failures: - -. Filter spans where `status.code` is `2` (error). -. Examine `status.message` for error details. -. Check the `events` array for error events with timestamps. -. Use `traceId` to correlate related spans and understand the full error context. -. Follow `parentSpanId` relationships to trace the error back to the originating tool. - -Example: A span with `status.code: 2` and `status.message: "connection timeout"` indicates the operation failed due to a timeout. - -== Correlate distributed operations - -Link MCP server activity to downstream effects: - -. Extract `traceId` from tool invocation spans. -. Search for the same `traceId` in other application logs or traces. -. Follow `parentSpanId` relationships to build complete operation timelines. -. Identify bottlenecks across your entire system. - -== Integrate with observability platforms - -The `redpanda.otel_traces` topic stores trace data in OpenTelemetry format. Redpanda does not support direct export to platforms like Grafana Cloud and Datadog due to format compatibility limitations. Redpanda produces one span per topic message, whereas these platforms expect traces in batch format. - -You can consume traces directly from the `redpanda.otel_traces` topic using any Kafka-compatible consumer for custom analysis and processing. - -== Next steps - -* xref:ai-agents:mcp/remote/concepts.adoc#transcripts[Transcripts]: Learn how traces and spans work -* xref:ai-agents:mcp/remote/troubleshooting.adoc[]: Diagnose and fix common issues -* xref:ai-agents:mcp/remote/manage-servers.adoc[]: Manage MCP server lifecycle diff --git a/modules/ai-agents/pages/observability/monitor-agents.adoc b/modules/ai-agents/pages/observability/monitor-agents.adoc deleted file mode 100644 index f72affafa..000000000 --- a/modules/ai-agents/pages/observability/monitor-agents.adoc +++ /dev/null @@ -1,226 +0,0 @@ -= Monitor Agent Activity -:description: Monitor agent execution, analyze conversation history, track token usage, and debug issues using inspector, transcripts, and agent data topics. -:page-topic-type: how-to -:personas: ai_agent_developer, platform_admin -:learning-objective-1: pass:q[Test agents interactively using the *Inspector* tab] -:learning-objective-2: Consume session and task topics for analysis -:learning-objective-3: Debug agent behavior using Transcripts - -Monitor your agents to track performance, analyze conversations, debug issues, and optimize costs. - -After reading this page, you will be able to: - -* [ ] {learning-objective-1} -* [ ] {learning-objective-2} -* [ ] {learning-objective-3} - -For conceptual background on traces and observability, see xref:ai-agents:observability/concepts.adoc[]. - -== Prerequisites - -You must have a running agent. If you do not have one, see xref:ai-agents:agents/quickstart.adoc[]. - -== Test agents interactively - -The *Inspector* tab provides real-time conversation testing and debugging. - -You can use the Inspector to: - -* Test agent responses interactively -* View full conversation history -* See tool invocations and results -* Monitor token usage per request -* Test error scenarios - -=== Access the inspector - -. Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud UI. -. Click your agent name. -. Open the *Inspector* tab. -. Enter test queries and review responses. -. Check the conversation panel to see tool calls. -. Start a new session to test fresh conversations. - -=== Testing best practices - -Test your agents systematically: - -* **Boundary cases**: Test requests at the edge of agent capabilities to verify scope enforcement -* **Error handling**: Request unavailable data to verify graceful degradation -* **Iteration count**: Monitor how many iterations complex requests require -* **Ambiguous input**: Send vague queries to verify clarification behavior -* **Token usage**: Track tokens per request to estimate costs - -== View execution traces - -The *Transcripts* view shows agent execution traces with detailed timing and error information. - -=== What Transcripts show - -* Agent startup and initialization -* Request and response flow -* Tool invocations and results -* Error messages and failures -* Token usage per request -* OpenTelemetry trace data - -=== Access Transcripts - -. Navigate to *Agentic AI* > *AI Agents*. -. Click your agent name. -. Click *Transcripts* in the left navigation. -. Select a transcript to view execution details. - -=== When to use Transcripts - -* Agent won't start (deployment issues) -* Tool execution errors -* Unexpected agent behavior -* Debugging conversation flow -* Verifying tool selection logic - -== Consume agent data topics - -Agents emit structured data to two Redpanda topics for monitoring and analysis. - -=== Sessions topic - -The sessions topic (`redpanda.aiagent..sessions`) contains all conversation messages. - -Use the sessions topic to: - -* Review past conversations -* Analyze conversation patterns -* Debug multi-turn interactions -* Understand context management - -=== Tasks topic - -The tasks topic (`redpanda.aiagent..tasks`) contains task execution records with status and artifacts. - -Use the tasks topic to: - -* Monitor task completion rates -* Track token usage and costs -* Debug failed tasks -* Analyze agent performance - -=== Access agent topics - -[tabs] -===== -Cloud UI:: -+ --- -. Navigate to *Topics* in the Redpanda Cloud UI. -. Find `redpanda.aiagent..sessions` or `redpanda.aiagent..tasks`. -. Click *Messages* to view recent data. -. Use filters to search for specific sessions or task states. --- - -rpk:: -+ --- -Consume recent sessions: - -[,bash] ----- -rpk topic consume redpanda.aiagent..sessions --offset end -n 10 ----- - -Consume recent tasks: - -[,bash] ----- -rpk topic consume redpanda.aiagent..tasks --offset end -n 10 ----- --- - -Data Plane API:: -+ --- -Use the link:/api/doc/cloud-dataplane/[Data Plane API] to programmatically consume agent data and integrate with your monitoring pipeline. --- -===== - -For schema details, see xref:ai-agents:agents/concepts.adoc#agent-data-topics[]. - -== Analyze conversation history - -Use conversation history to identify behavior patterns and diagnose issues. - -=== Common patterns - -Look for these indicators when analyzing conversations: - -* Agent calling the same tool repeatedly indicates loop detection is needed -* Large gaps between messages suggest tool timeout or slow execution -* Agent responses without tool calls indicate a tool selection issue -* Fabricated information suggests a missing "never make up data" constraint -* Truncated early messages indicate the context window was exceeded - -=== Analysis workflow - -. Use the inspector to reproduce the issue. -. Review full conversation including tool invocations. -. Identify where agent behavior diverged from expected. -. Check system prompt for missing guidance. -. Verify tool responses are formatted correctly. - -== Track token usage - -Monitor token consumption to optimize costs and set appropriate iteration limits. - -=== View token usage - -Token usage appears in: - -* **Inspector tab**: Shows tokens per request during interactive testing -* **Tasks topic**: Contains `usage` metadata with input/output/total tokens -* **Transcripts**: Displays input and output token counts for each execution - -=== Calculate costs - -Use token data from the tasks topic to estimate costs: - -[,bash] ----- -Cost per request = (total_tokens × model_price_per_token) ----- - -Track costs over time by: - -. Consuming the tasks topic. -. Extracting `metadata.usage.total_tokens` from each task. -. Multiplying by your model's token price. -. Aggregating by time period. - -For cost optimization strategies, see xref:ai-agents:agents/concepts.adoc#cost-calculation[]. - -== Monitor performance - -Track agent performance metrics to identify bottlenecks. - -=== Key metrics - -Monitor these metrics from agent data topics: - -* **Task completion rate**: Percentage of tasks with `TASK_STATE_COMPLETED` -* **Average iterations**: Mean iterations per task from conversation history -* **Token efficiency**: Tokens consumed per successful task completion -* **Tool invocation frequency**: Which tools are called most often -* **Error rate**: Percentage of tasks with `TASK_STATE_FAILED` - -=== Performance analysis - -. Consume the tasks topic over a time window. -. Calculate completion rate: `completed_tasks / total_tasks`. -. Identify high iteration tasks for prompt optimization. -. Track token usage trends over time. -. Correlate errors with specific tool invocations. - -== Next steps - -* xref:ai-agents:observability/concepts.adoc[]: Understand trace structure and OpenTelemetry spans -* xref:ai-agents:agents/troubleshooting.adoc[]: Diagnose and fix common agent issues -* xref:ai-agents:agents/concepts.adoc[]: Learn about agent execution and cost calculation From 8f083fd5919cf6967ef6fc849521fc26a91ee366 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Mon, 2 Feb 2026 09:49:40 +0000 Subject: [PATCH 82/97] Integrate AI Gateway into agent documentation - Replace API key prerequisites with AI Gateway requirement - Add gateway selection before provider/model selection - Remove 'Add API keys as secrets' section from create-agent - Update all tutorials to use gateway-based authentication --- .../ai-agents/pages/agents/create-agent.adoc | 23 +++++++------------ .../ai-agents/pages/agents/quickstart.adoc | 13 ++++------- .../tutorials/customer-support-agent.adoc | 7 +++--- .../transaction-dispute-resolution.adoc | 6 ++--- 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/modules/ai-agents/pages/agents/create-agent.adoc b/modules/ai-agents/pages/agents/create-agent.adoc index 5103fb3ce..ef5d2950f 100644 --- a/modules/ai-agents/pages/agents/create-agent.adoc +++ b/modules/ai-agents/pages/agents/create-agent.adoc @@ -16,9 +16,9 @@ After reading this page, you will be able to: == Prerequisites -* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] on AWS running Redpanda version 25.3 and later. +* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] with Remote MCP enabled. +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[AI Gateway configured] with at least one LLM provider enabled. * At least one xref:ai-agents:mcp/remote/overview.adoc[Remote MCP server] deployed with tools. -* LLM provider API key (OpenAI, Google, or Anthropic). * System prompt prepared (see xref:ai-agents:agents/prompt-best-practices.adoc[System Prompt Best Practices]). == Access the agents UI @@ -59,7 +59,11 @@ Start with Medium for production workloads. Monitor CPU and memory usage, then a Agents use large language models (LLMs) to interpret user intent and decide which tools to invoke. -. In the *Model* section, select your LLM provider: +. Select your AI Gateway: ++ +Choose the gateway that contains your configured LLM providers and API keys. If you have multiple gateways, select the appropriate one for this agent's workload (for example, production vs staging, or team-specific gateways). + +. Select your LLM provider from those available in the gateway: + * OpenAI (GPT models) * Google (Gemini models) @@ -74,7 +78,7 @@ Agents use large language models (LLMs) to interpret user intent and decide whic . Select the specific model version from the dropdown. + -The Console shows available models with descriptions. +The dropdown shows available models with descriptions. For detailed model specifications and pricing: @@ -84,17 +88,6 @@ For detailed model specifications and pricing: For model selection based on architecture patterns, see xref:ai-agents:agents/architecture-patterns.adoc#model-selection-guide[Model selection guide]. -== Add API keys as secrets - -. In the *API Key* section, click *Add Secret*. -. Choose one: -+ -* *Use existing secret*: Select from your Secrets Store -* *Create new secret*: Enter your LLM provider API key - -. Name your secret (for example: `openai-api-key`). -. Click *Save*. - == Write the system prompt . In the *System Prompt* section, enter your prompt (minimum 10 characters). diff --git a/modules/ai-agents/pages/agents/quickstart.adoc b/modules/ai-agents/pages/agents/quickstart.adoc index dcfe2b0cf..7417e6206 100644 --- a/modules/ai-agents/pages/agents/quickstart.adoc +++ b/modules/ai-agents/pages/agents/quickstart.adoc @@ -18,17 +18,13 @@ After completing this quickstart, you will be able to: * A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] (agents are not available on Dedicated or Serverless clusters) +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[AI Gateway configured] with at least one LLM provider enabled (OpenAI, Anthropic, or Google AI) + * Completed the xref:ai-agents:mcp/remote/quickstart.adoc[Remote MCP Quickstart] to create an MCP server with the following tools deployed: + ** `generate_input`: Generates fake user event data ** `redpanda_output`: Publishes data to Redpanda topics -* API key from one of these providers: -+ -** link:https://platform.openai.com/api-keys[OpenAI API key^] -** link:https://console.anthropic.com/settings/keys[Anthropic API key^] -** link:https://aistudio.google.com/app/apikey[Google AI API key^] - == What you'll build An Event Data Manager agent that: @@ -53,9 +49,10 @@ The agent orchestrates the `generate_input` and `redpanda_output` tools you crea * *Description*: `Generates and publishes fake user event data to Redpanda topics` * *Resource Tier*: Select *XSmall* (sufficient for this quickstart) -. Choose your LLM provider and model: +. Select your AI Gateway and model: + -* *Provider*: Select OpenAI, Anthropic, or Google +* *AI Gateway*: Select the gateway you configured (contains provider and API key configuration) +* *Provider*: Select a provider available in your gateway (OpenAI, Anthropic, or Google) * *Model*: Choose any balanced model from the dropdown . Add your API key: diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index edf0edeae..a3778a34c 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -36,8 +36,8 @@ The challenge: users phrase requests differently ("Where's my package?", "Track == Prerequisites -* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] on AWS running Redpanda version 25.3 and later. -* LLM provider API key (this tutorial uses OpenAI). +* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] with Remote MCP enabled. +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[AI Gateway configured] with at least one LLM provider enabled (this tutorial uses OpenAI). == Design the MCP tools @@ -120,8 +120,9 @@ Create the customer support agent with the system prompt. * *Name*: `customer-support-agent` * *Description*: `Helps customers track orders and shipping` * *Resource Tier*: Medium +* *AI Gateway*: Select the gateway you configured +* *Provider*: OpenAI or Anthropic * *Model*: OpenAI GPT-5.2 or Claude Sonnet 4.5 (models with strong reasoning) -* *API Key*: Your LLM provider API key * *MCP Server*: Select `customer-support-tools` * *Max Iterations*: 15 diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index 4d3b2b955..f1ccd97ac 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -32,8 +32,8 @@ When a customer calls saying "I see a $247.83 charge from 'ACME CORP' but I neve == Prerequisites -* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] on AWS running Redpanda version 25.3 and later. -* LLM provider API key (this tutorial uses OpenAI GPT-5.2 or Claude Sonnet 4.5 for reasoning). +* A xref:get-started:cluster-types/byoc/index.adoc[BYOC cluster] with Remote MCP enabled. +* xref:ai-agents:ai-gateway/gateway-quickstart.adoc[AI Gateway configured] with at least one LLM provider enabled (this tutorial uses OpenAI GPT-5.2 or Claude Sonnet 4.5 for reasoning). * The xref:get-started:rpk-install.adoc[Redpanda CLI (`rpk`)] installed (for testing the pipeline with sample data). * Completed xref:ai-agents:agents/tutorials/customer-support-agent.adoc[] (foundational multi-tool concepts). @@ -225,9 +225,9 @@ Sub-agents inherit the LLM provider, model, resource tier, and max iterations fr * *Name*: `dispute-resolution-agent` * *Description*: `Orchestrates transaction dispute investigations` * *Resource Tier*: Large +* *AI Gateway*: Select the gateway you configured * *Provider*: OpenAI * *Model*: GPT-5 Mini (fast, cost-effective for structured workflows) -* *API Key*: Your LLM provider API key * *Max Iterations*: 15 . In the *System Prompt* field, enter: From 1e0d4c5a42644d20231a95ffdeb243eee42a74d0 Mon Sep 17 00:00:00 2001 From: Jake Cahill <45230295+JakeSCahill@users.noreply.github.com> Date: Mon, 2 Feb 2026 09:51:54 +0000 Subject: [PATCH 83/97] Update local-antora-playbook.yml --- local-antora-playbook.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/local-antora-playbook.yml b/local-antora-playbook.yml index 3e5b6c1d8..d8d478c82 100644 --- a/local-antora-playbook.yml +++ b/local-antora-playbook.yml @@ -10,9 +10,6 @@ urls: latest_version_segment: 'current' output: clean: true -runtime: - log: - failure_level: error content: sources: - url: . From 722c22828610ec2517407046c4140cf648229bde Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Mon, 2 Feb 2026 17:36:06 +0000 Subject: [PATCH 84/97] Replace 'Cloud UI' with 'Cloud Console' throughout ai-agents docs --- .../examples/mcp-tools/test-mcp-tools.sh | 2 +- modules/ai-agents/pages/agents/a2a-concepts.adoc | 2 +- .../pages/agents/architecture-patterns.adoc | 2 +- modules/ai-agents/pages/agents/create-agent.adoc | 6 +++--- .../ai-agents/pages/agents/monitor-agents.adoc | 2 +- modules/ai-agents/pages/agents/quickstart.adoc | 2 +- .../agents/tutorials/customer-support-agent.adoc | 4 ++-- .../transaction-dispute-resolution.adoc | 10 +++++----- .../ai-agents/pages/mcp/remote/create-tool.adoc | 8 ++++---- .../pages/mcp/remote/manage-servers.adoc | 16 ++++++++-------- .../pages/mcp/remote/monitor-mcp-servers.adoc | 6 +++--- .../ai-agents/pages/mcp/remote/quickstart.adoc | 6 +++--- .../pages/mcp/remote/scale-resources.adoc | 4 ++-- 13 files changed, 35 insertions(+), 35 deletions(-) diff --git a/modules/ai-agents/examples/mcp-tools/test-mcp-tools.sh b/modules/ai-agents/examples/mcp-tools/test-mcp-tools.sh index 263ea5dbf..f815a561f 100755 --- a/modules/ai-agents/examples/mcp-tools/test-mcp-tools.sh +++ b/modules/ai-agents/examples/mcp-tools/test-mcp-tools.sh @@ -12,7 +12,7 @@ # # Unlike rp-connect-docs, Cloud MCP tools cannot be tested with # `rpk connect run` because they are standalone tool definitions, not -# full pipelines. End-to-end testing requires the Cloud UI. +# full pipelines. End-to-end testing requires the Cloud Console. set -euo pipefail diff --git a/modules/ai-agents/pages/agents/a2a-concepts.adoc b/modules/ai-agents/pages/agents/a2a-concepts.adoc index 48599c2a3..675142eb0 100644 --- a/modules/ai-agents/pages/agents/a2a-concepts.adoc +++ b/modules/ai-agents/pages/agents/a2a-concepts.adoc @@ -38,7 +38,7 @@ The agent card is a JSON document that describes what the agent can do and how t [#agent-card-location] === Agent card location -Redpanda Cloud agents expose their agent cards at the `/.well-known/agent-card.json` subpath of the agent URL. You can find the agent URL on the agent overview page in the Redpanda Cloud UI under *Agentic AI* > *AI Agents*. +Redpanda Cloud agents expose their agent cards at the `/.well-known/agent-card.json` subpath of the agent URL. You can find the agent URL on the agent overview page in the Redpanda Cloud Console under *Agentic AI* > *AI Agents*. For example, if your agent URL is `\https://my-agent.ai-agents.abc123.cloud.redpanda.com`, your agent card URL is `\https://my-agent.ai-agents.abc123.cloud.redpanda.com/.well-known/agent-card.json`. diff --git a/modules/ai-agents/pages/agents/architecture-patterns.adoc b/modules/ai-agents/pages/agents/architecture-patterns.adoc index dbfbd3dae..aeed99dde 100644 --- a/modules/ai-agents/pages/agents/architecture-patterns.adoc +++ b/modules/ai-agents/pages/agents/architecture-patterns.adoc @@ -166,7 +166,7 @@ Design workflows to complete in 20-30 iterations. Return paginated results from == Model selection guide -Choose models based on task complexity, latency requirements, and cost constraints. The Redpanda Cloud UI displays available models with descriptions when creating agents. +Choose models based on task complexity, latency requirements, and cost constraints. The Redpanda Cloud Console displays available models with descriptions when creating agents. === Match models to task complexity diff --git a/modules/ai-agents/pages/agents/create-agent.adoc b/modules/ai-agents/pages/agents/create-agent.adoc index ef5d2950f..60b95ecf9 100644 --- a/modules/ai-agents/pages/agents/create-agent.adoc +++ b/modules/ai-agents/pages/agents/create-agent.adoc @@ -6,7 +6,7 @@ :learning-objective-2: Connect MCP servers and select tools for your agent :learning-objective-3: Set agent execution parameters including max iterations -Create a new AI agent through the Redpanda Cloud UI. This guide walks you through configuring the agent's model, system prompt, tools, and execution settings. +Create a new AI agent through the Redpanda Cloud Console. This guide walks you through configuring the agent's model, system prompt, tools, and execution settings. After reading this page, you will be able to: @@ -23,7 +23,7 @@ After reading this page, you will be able to: == Access the agents UI -. Log in to the link:https://cloud.redpanda.com[Redpanda Cloud UI^]. +. Log in to the link:https://cloud.redpanda.com[Redpanda Cloud Console^]. . Navigate to your cluster. . Click *Agentic AI* > *AI Agents* in the left navigation. @@ -226,7 +226,7 @@ https://.ai-agents.. You can use this URL to call your agent programmatically or integrate it with external systems. -The *Inspector* tab in the Cloud UI automatically uses this URL to connect to your agent for testing. +The *Inspector* tab in the Cloud Console automatically uses this URL to connect to your agent for testing. For programmatic access or external agent integration, see xref:ai-agents:agents/integration-overview.adoc[]. diff --git a/modules/ai-agents/pages/agents/monitor-agents.adoc b/modules/ai-agents/pages/agents/monitor-agents.adoc index 71578c7f6..c493aa291 100644 --- a/modules/ai-agents/pages/agents/monitor-agents.adoc +++ b/modules/ai-agents/pages/agents/monitor-agents.adoc @@ -79,7 +79,7 @@ The *Inspector* tab provides real-time conversation testing. Use it to test agen === Access Inspector -. Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud UI. +. Navigate to *Agentic AI* > *AI Agents* in the Redpanda Cloud Console. . Click your agent name. . Open the *Inspector* tab. . Enter test queries and review responses. diff --git a/modules/ai-agents/pages/agents/quickstart.adoc b/modules/ai-agents/pages/agents/quickstart.adoc index 7417e6206..0be1a183b 100644 --- a/modules/ai-agents/pages/agents/quickstart.adoc +++ b/modules/ai-agents/pages/agents/quickstart.adoc @@ -37,7 +37,7 @@ The agent orchestrates the `generate_input` and `redpanda_output` tools you crea == Create the agent -. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud UI^]. +. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud Console^]. . Navigate to your cluster and click *Agentic AI* > *AI Agents* in the left navigation. diff --git a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc index a3778a34c..552a5bdce 100644 --- a/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc +++ b/modules/ai-agents/pages/agents/tutorials/customer-support-agent.adoc @@ -61,7 +61,7 @@ This granularity enables the agent to chain tools (check order status, see it's Create a Remote MCP server with the three tools. -. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud UI^]. +. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud Console^]. . Go to *Agentic AI* > *Remote MCP*. . Click *Create MCP Server*. . Configure the server: @@ -180,7 +180,7 @@ Wait for the agent status to show *Running*. == Observe orchestration in action -Open the *Inspector* tab in the Redpanda Cloud UI to interact with the agent. +Open the *Inspector* tab in the Redpanda Cloud Console to interact with the agent. Testing reveals how the agent makes decisions. Watch the conversation panel in the built-in chat interface to see the agent's reasoning process unfold. diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index f1ccd97ac..b4eb3670f 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -45,7 +45,7 @@ Before creating agents, create the tools they'll use. You'll organize tools by d Account tools retrieve customer and transaction data with PII protection. -. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud UI^]. +. Navigate to your cluster in the link:https://cloud.redpanda.com[Redpanda Cloud Console^]. . Go to *Agentic AI* > *Remote MCP*. . Click *Create MCP Server*. . Configure the server: @@ -441,7 +441,7 @@ https://abc123.ai-agents.def456.cloud.redpanda.com/.well-known/agent-card.json Create the topics the pipeline will use for input and output. -. Go to *Topics* in the Redpanda Cloud UI. +. Go to *Topics* in the Redpanda Cloud Console. . Click *Create Topic*. . Create the input topic: + @@ -460,7 +460,7 @@ Create the topics the pipeline will use for input and output. The pipeline needs SASL credentials to read from and write to Redpanda topics. -. Go to *Security* > *Users* in the Redpanda Cloud UI. +. Go to *Security* > *Users* in the Redpanda Cloud Console. . Click *Create User*. . Configure the user: + @@ -496,7 +496,7 @@ The pipeline needs SASL credentials to read from and write to Redpanda topics. The pipeline needs SASL credentials stored as secrets to authenticate with Redpanda topics. -. Go to *Connect* > *Secrets* in the Redpanda Cloud UI (if not already there). +. Go to *Connect* > *Secrets* in the Redpanda Cloud Console (if not already there). . Click *Create Secret*. . Create two secrets with these values: + @@ -505,7 +505,7 @@ The pipeline needs SASL credentials stored as secrets to authenticate with Redpa === Create the pipeline -. Go to *Connect* in the Redpanda Cloud UI. +. Go to *Connect* in the Redpanda Cloud Console. . Click *Create Pipeline*. . In the numbered steps, click *4 Add permissions*. . Select *Service Account*. diff --git a/modules/ai-agents/pages/mcp/remote/create-tool.adoc b/modules/ai-agents/pages/mcp/remote/create-tool.adoc index 1d60444bf..3b13dcec4 100644 --- a/modules/ai-agents/pages/mcp/remote/create-tool.adoc +++ b/modules/ai-agents/pages/mcp/remote/create-tool.adoc @@ -24,14 +24,14 @@ After reading this page, you will be able to: == Create the tool -In Redpanda Cloud, you create tools directly in the Cloud UI or using the Data Plane API. +In Redpanda Cloud, you create tools directly in the Cloud Console or using the Data Plane API. [tabs] ====== -Cloud UI:: +Cloud Console:: + -- -. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud UI^]. +. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud Console^]. . Navigate to *Agentic AI* > *Remote MCP* and either create a new MCP server or edit an existing one. @@ -207,7 +207,7 @@ Reference secrets using `${secrets.SECRET_NAME}` syntax: include::ai-agents:example$mcp-tools/snippets/secrets.yaml[tag=example,indent=0] ---- -When you add secret references to your tool configuration, the Cloud UI automatically detects them and provides an interface to create the required secrets. +When you add secret references to your tool configuration, the Cloud Console automatically detects them and provides an interface to create the required secrets. === Secrets best practices diff --git a/modules/ai-agents/pages/mcp/remote/manage-servers.adoc b/modules/ai-agents/pages/mcp/remote/manage-servers.adoc index 61d7e66e7..40fe836f7 100644 --- a/modules/ai-agents/pages/mcp/remote/manage-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/manage-servers.adoc @@ -27,10 +27,10 @@ You can update the configuration, resources, or metadata of an MCP server at any [tabs] ===== -Cloud UI:: +Cloud Console:: + -- -. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. . Find the MCP server you want to edit and click its name. . Click *Edit configuration*. . Make your changes. @@ -70,10 +70,10 @@ Stopping a server pauses all tool execution and releases compute resources, but [tabs] ===== -Cloud UI:: +Cloud Console:: + -- -. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. . Find the server you want to stop. . Click the three dots and select *Stop*. . Confirm the action. @@ -101,10 +101,10 @@ Resume a stopped server to restore its functionality. [tabs] ===== -Cloud UI:: +Cloud Console:: + -- -. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. . Find the stopped server. . Click the three dots and select *Start*. . Wait for the status to show *Running* before reconnecting clients. @@ -132,10 +132,10 @@ Deleting a server permanently removes it. You cannot undo this action. Redpanda [tabs] ===== -Cloud UI:: +Cloud Console:: + -- -. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. . Find the server you want to delete. . Click the three dots and select *Delete*. . Confirm the deletion when prompted. diff --git a/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc index fbb331875..7966fc3ae 100644 --- a/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc +++ b/modules/ai-agents/pages/mcp/remote/monitor-mcp-servers.adoc @@ -20,7 +20,7 @@ For conceptual background on traces, spans, and the trace data structure, see xr You must have an existing MCP server. If you do not have one, see xref:ai-agents:mcp/remote/quickstart.adoc[]. -== View transcripts in the Cloud UI +== View transcripts in the Cloud Console :context: mcp include::ai-agents:partial$transcripts-ui-guide.adoc[] @@ -33,10 +33,10 @@ MCP servers emit OpenTelemetry traces to the `redpanda.otel_traces` topic. Consu [tabs] ===== -Cloud UI:: +Cloud Console:: + -- -. In the Redpanda Cloud UI, navigate to *Topics*. +. In the Redpanda Cloud Console, navigate to *Topics*. . Select `redpanda.otel_traces`. . Click *Messages* to view recent traces. . Use filters to search for specific trace IDs, span names, or time ranges. diff --git a/modules/ai-agents/pages/mcp/remote/quickstart.adoc b/modules/ai-agents/pages/mcp/remote/quickstart.adoc index 32a41b5aa..92ee94aeb 100644 --- a/modules/ai-agents/pages/mcp/remote/quickstart.adoc +++ b/modules/ai-agents/pages/mcp/remote/quickstart.adoc @@ -176,10 +176,10 @@ curl -X POST "https:///v1/acls" \ [tabs] ===== -Cloud UI:: +Cloud Console:: + -- -. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud UI^]. +. Log in to the link:https://cloud.redpanda.com/[Redpanda Cloud Console^]. . Navigate to *Agentic AI* > *Remote MCP*. + @@ -187,7 +187,7 @@ This page shows a list of existing servers. . Click *Create new MCP Server*. In *Server Metadata*, configure the basic information and resources: + -* *Display Name*: A human-friendly name such as `event-data-generator`. This name is shown in the Redpanda Cloud UI. It is not the name of the MCP server itself. +* *Display Name*: A human-friendly name such as `event-data-generator`. This name is shown in the Redpanda Cloud Console. It is not the name of the MCP server itself. * *Description*: Explain what the server does. For example, `Generates fake user event data and publishes it to Redpanda topics`. * *Tags*: Add key/value tags such as `owner=platform` or `env=demo`. The tag names `service_account_id` and `secret_id` are reserved and cannot be used. * *Resources*: Choose a size (XSmall / Small / Medium / Large / XLarge). Larger sizes allow more concurrent requests and faster processing, but cost more. You can change this later. diff --git a/modules/ai-agents/pages/mcp/remote/scale-resources.adoc b/modules/ai-agents/pages/mcp/remote/scale-resources.adoc index 424094577..3c4d948b7 100644 --- a/modules/ai-agents/pages/mcp/remote/scale-resources.adoc +++ b/modules/ai-agents/pages/mcp/remote/scale-resources.adoc @@ -24,10 +24,10 @@ You must have an existing MCP server. If you do not have one, see xref:ai-agents [tabs] ===== -Cloud UI:: +Cloud Console:: + -- -. In the Redpanda Cloud UI, navigate to *Agentic AI* > *Remote MCP*. +. In the Redpanda Cloud Console, navigate to *Agentic AI* > *Remote MCP*. . Find the MCP server you want to scale and click its name. . Click *Edit configuration*. . Under *Resources*, select a new size: From 6bac58b5b3da6c2d91e9406c298558ebca011ea6 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Mon, 2 Feb 2026 19:28:45 +0000 Subject: [PATCH 85/97] Update MCP overview --- modules/ai-agents/pages/mcp/overview.adoc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/mcp/overview.adoc b/modules/ai-agents/pages/mcp/overview.adoc index 964be2dc5..1357d7211 100644 --- a/modules/ai-agents/pages/mcp/overview.adoc +++ b/modules/ai-agents/pages/mcp/overview.adoc @@ -18,7 +18,11 @@ After reading this page, you will be able to: == What is MCP? -MCP (Model Context Protocol) is an open standard that lets AI agents use tools. Think of it like a universal adapter: instead of building custom integrations for every AI system, you define your tools once using MCP, and any MCP-compatible AI client can discover and use them. +The Model Context Protocol (MCP) provides a standardized way for AI agents to connect with external data sources and tools in Redpanda Cloud. + +Each MCP server hosts a set of tools that AI clients can discover and invoke. Tools are custom integrations that expose data, APIs, or workflows to AI agents. + +Think of MCP like a universal adapter: instead of building custom integrations for every AI system, you define your tools once using MCP, and any MCP-compatible AI client can discover and use them. Without MCP, connecting AI to your business systems requires custom API code, authentication handling, and response formatting for each AI platform. With MCP, you describe what a tool does and what inputs it needs, and the protocol handles the rest. From b5b404ceba0565fc54c1d41140cfdfbcf236f26d Mon Sep 17 00:00:00 2001 From: Kat Batuigas Date: Mon, 2 Feb 2026 15:14:46 -0800 Subject: [PATCH 86/97] Remove search and add placeholder for filtering functionality --- .../pages/observability/view-transcripts.adoc | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/modules/ai-agents/pages/observability/view-transcripts.adoc b/modules/ai-agents/pages/observability/view-transcripts.adoc index f21ee804e..851c9f9bc 100644 --- a/modules/ai-agents/pages/observability/view-transcripts.adoc +++ b/modules/ai-agents/pages/observability/view-transcripts.adoc @@ -1,12 +1,12 @@ = View Transcripts -:description: Learn how to filter, search, and navigate the Transcripts interface to investigate agent execution traces using multiple detail views and interactive timeline navigation. +:description: Learn how to filter and navigate the Transcripts interface to investigate agent execution traces using multiple detail views and interactive timeline navigation. :page-topic-type: how-to :personas: agent_developer, platform_admin -:learning-objective-1: Filter and search transcripts to find specific execution traces +:learning-objective-1: Filter transcripts to find specific execution traces :learning-objective-2: Navigate between detail views to inspect span information at different levels :learning-objective-3: Use the timeline interactively to navigate to specific time periods -The Transcripts view provides filtering, searching, and navigation capabilities for investigating agent and MCP server execution transcripts. Use these features to efficiently locate specific operations, analyze performance patterns, and debug issues across tool invocations, LLM calls, and agent reasoning steps. +The Transcripts view provides filtering and navigation capabilities for investigating agent, MCP server, and AI Gateway execution glossterm:transcript[transcripts]. Use this view to quickly locate specific operations, analyze performance patterns, and debug issues across glossterm:tool[] invocations, LLM calls, and glossterm:agent[] reasoning steps. After reading this page, you will be able to: @@ -14,7 +14,13 @@ After reading this page, you will be able to: * [ ] {learning-objective-2} * [ ] {learning-objective-3} -For basic orientation on agent and MCP server monitoring, see xref:ai-agents:agents/monitor-agents.adoc[] or xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[]. For conceptual background on what transcripts capture and how spans are organized hierarchically, see xref:ai-agents:observability/concepts.adoc[]. +For basic orientation on monitoring each Redpanda Agentic Data Plane component, see: + +* xref:ai-agents:ai-gateway/observability-metrics.adoc[] +* xref:ai-agents:agents/monitor-agents.adoc[] +* xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] + +For conceptual background on what transcripts capture and how glossterm:span[spans] are organized hierarchically, see xref:ai-agents:observability/concepts.adoc[]. == Prerequisites @@ -27,37 +33,17 @@ For basic orientation on agent and MCP server monitoring, see xref:ai-agents:age Use the timeline visualization to quickly identify when errors began or patterns changed, and navigate directly to transcripts from particular timestamps. -When viewing time periods with many transcripts (hundreds or thousands), the timeline displays a subset of the data to maintain performance and usability. The timeline bar indicates the actual time range of currently visible data, which may be narrower than your selected range. +When viewing time periods with many transcripts (hundreds or thousands), the timeline displays a subset of the data to maintain performance and usability. The timeline bar indicates the actual time range of currently visible data, which may be narrower than your <>. TIP: See xref:ai-agents:agents/monitor-agents.adoc[] and xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[] to learn basic execution patterns and health indicators to investigate. -=== Search and filter for transcripts - -Use search and filters together to narrow down transcripts and quickly locate specific executions. - -==== Search for specific transcripts +=== Filter transcripts -The search functionality helps you find transcripts by operation names, span types, or identifiers: +Use filters to narrow down transcripts and quickly locate specific executions. When you use any of the filters, the transcript list updates to show only matching results. You can toggle *Full transcript* on to see the complete execution context, in grayed-out text, for the filtered transcripts. -* Search by span names to find specific xref:ai-agents:observability/concepts.adoc#agent-span-types[agent operations] like `invoke_agent`, or xref:ai-agents:mcp/remote/create-tool.adoc[MCP tools] -* Search by xref:ai-agents:observability/concepts.adoc#instrumentation-layers[scope] to filter by layer (for example, `rpcn-mcp` for MCP tool spans) -* Search by trace IDs (`traceId`) when correlating with external systems or troubleshooting specific requests +==== Filter by attribute -==== Filter by service - -Service filtering shows only transcripts from specific agents or MCP servers using the `service.name` resource attribute. See xref:ai-agents:observability/concepts.adoc#cross-service-transcripts[Cross-service transcripts] to understand how transcripts span multiple services. - -* View executions from a single agent when multiple are running (service name: `ai-agent`) -* Isolate MCP server activity from agent activity (service name: `mcp-{server-id}`) -* Compare behavior across different service instances - -==== Filter by execution status - -Status filtering shows transcripts based on their execution outcome: - -* Show successful executions for health checks -* Show only failed executions for error investigation -* Toggle between success and error views to compare and analyze patterns +// Add details when available ==== Adjust time range @@ -67,15 +53,13 @@ Use the time range selector to focus on specific time periods (from the last fiv * Expand to longer periods for trend analysis over the last day * Narrow to specific time windows when investigating issues that occurred at known times -TIP: Apply broad filters first (time range, service) to reduce the transcript set, then use search to narrow to specific operations. - == Inspect span details -Each row in the transcript table represents a high-level agent or MCP server request flow. Expand each parent span to see the xref:ai-agents:observability/concepts.adoc#agent-transcript-hierarchy[hierarchical structure] of nested operations, including tool calls, LLM interactions, and internal processing steps. Parent-child spans show how operations relate: for example, an agent invocation (parent) triggers LLM calls and tool executions (children). +Each row in the transcript table represents a high-level agent or MCP server request flow. Expand each parent glossterm:span[] to see the xref:ai-agents:observability/concepts.adoc#agent-transcript-hierarchy[hierarchical structure] of nested operations, including tool calls, LLM interactions, and internal processing steps. Parent-child spans show how operations relate: for example, an agent invocation (parent) triggers LLM calls and tool executions (children). -When agents invoke remote MCP servers, transcripts fold together across service boundaries to provide a unified view of the complete operation. The trace ID originates at the initial request touchpoint and propagates across all involved services, linking spans from both the agent and MCP server under a single transcript. Use the tree view to follow the trace flow across multiple services and understand the complete request lifecycle. +When agents invoke remote MCP servers, transcripts fold together under a tree structure to provide a unified view of the complete operation across service boundaries. The glossterm:trace ID[] originates at the initial request touchpoint and propagates across all involved services, linking spans from both the agent and MCP server under a single transcript. Use the tree view to follow the trace flow across multiple services and understand the complete request lifecycle. -If you use external agents that directly invoke MCP servers in the Redpanda Agentic Data Plane, you may only see MCP-level parent transcripts, unless you have configured the agents to also emit traces to the Redpanda OTEL ingestion pipeline. +If you use external agents that directly invoke MCP servers in the Redpanda Agentic Data Plane, you may only see MCP-level parent transcripts, unless you have configured the agents to also emit traces to the Redpanda glossterm:OpenTelemetry[OTEL] ingestion pipeline. Selected spans display detailed information at multiple levels, from high-level summaries to complete raw data: @@ -100,7 +84,7 @@ TIP: Expand the summary panel to full view to easily read long conversations. === Detailed attributes view -The attributes view shows structured metadata for each transcript span. Use this view to quickly locate an attribute value such as conversation ID, then paste it into the search box to find all operations from that conversation session. See xref:ai-agents:observability/concepts.adoc#key-attributes-by-layer[Transcripts and AI Observability] for details on standard attributes by instrumentation layer. +The attributes view shows structured metadata for each transcript span. Use this view to inspect span attributes and understand the context of each operation. See xref:ai-agents:observability/concepts.adoc#key-attributes-by-layer[Transcripts and AI Observability] for details on standard attributes by instrumentation layer. === Raw data view From be98ee0569d12c8e816fa71e3cda9eff2bebdf0c Mon Sep 17 00:00:00 2001 From: Kat Batuigas Date: Mon, 2 Feb 2026 15:15:16 -0800 Subject: [PATCH 87/97] Clarify trace format --- modules/ai-agents/pages/observability/concepts.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/observability/concepts.adoc b/modules/ai-agents/pages/observability/concepts.adoc index 5cf30a1b1..1d891dd9c 100644 --- a/modules/ai-agents/pages/observability/concepts.adoc +++ b/modules/ai-agents/pages/observability/concepts.adoc @@ -316,7 +316,7 @@ The `events` array captures what happened and when. Use `timeUnixNano` to see ex [[opentelemetry-traces-topic]] == How Redpanda stores trace data -The `redpanda.otel_traces` topic stores OpenTelemetry spans using Redpanda's Schema Registry wire format with a custom Protobuf schema named `redpanda.otel_traces-value` that closely follows the https://opentelemetry.io/docs/specs/otel/protocol/[OpenTelemetry Protocol (OTLP)^] specification. This schema is automatically registered in the Schema Registry with the topic, enabling clients to deserialize trace data correctly. +The `redpanda.otel_traces` topic stores OpenTelemetry spans using Redpanda's Schema Registry wire format, with a custom Protobuf schema named `redpanda.otel_traces-value` that follows the https://opentelemetry.io/docs/specs/otel/protocol/[OpenTelemetry Protocol (OTLP)^] specification. Spans include attributes following OpenTelemetry https://opentelemetry.io/docs/specs/semconv/gen-ai/[semantic conventions for generative AI^], such as `gen_ai.operation.name` and `gen_ai.conversation.id`. The schema is automatically registered in the Schema Registry with the topic, so Kafka clients can consume and deserialize trace data correctly. Redpanda manages both the `redpanda.otel_traces` topic and its schema automatically. If you delete either the topic or the schema, they are recreated automatically. However, deleting the topic permanently deletes all trace data, and the topic comes back empty. Do not produce your own data to this topic. It is reserved for OpenTelemetry traces. From 9771301660d7a2d30487ade15129dfb42e3aeef1 Mon Sep 17 00:00:00 2001 From: Kat Batuigas Date: Mon, 26 Jan 2026 18:58:08 -0800 Subject: [PATCH 88/97] Start telemetry doc for BYOA --- .../observability/ingest-custom-traces.adoc | 457 ++++++++++++++++++ 1 file changed, 457 insertions(+) create mode 100644 modules/ai-agents/pages/observability/ingest-custom-traces.adoc diff --git a/modules/ai-agents/pages/observability/ingest-custom-traces.adoc b/modules/ai-agents/pages/observability/ingest-custom-traces.adoc new file mode 100644 index 000000000..96a8656bd --- /dev/null +++ b/modules/ai-agents/pages/observability/ingest-custom-traces.adoc @@ -0,0 +1,457 @@ += Ingest OpenTelemetry Traces from Custom Agents +:description: Configure a Redpanda Connect pipeline to ingest OTEL traces from custom agents into Redpanda for unified observability. +:page-topic-type: how-to +:learning-objective-1: Configure a Redpanda Connect pipeline to receive OpenTelemetry traces from custom agents via HTTP and publish them to redpanda.otel_traces +:learning-objective-2: Validate trace data format and compatibility with existing MCP server traces +:learning-objective-3: Secure the ingestion endpoint using authentication mechanisms + +When you build custom agents or instrument applications outside of Remote MCP servers and declarative agents, you can send OpenTelemetry (OTEL) traces to Redpanda for centralized observability. Deploy a Redpanda Connect pipeline as an HTTP ingestion endpoint to collect and publish traces to the `redpanda.otel_traces` topic. + +After reading this page, you will be able to: + +* [ ] {learning-objective-1} +* [ ] {learning-objective-2} +* [ ] {learning-objective-3} + +== Prerequisites + +* A BYOC cluster +* Ability to manage secrets in Redpanda Cloud +* The latest version of `rpk` installed +* Custom agent or application instrumented with OpenTelemetry SDK +* Basic understanding of the https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/[OpenTelemetry span format^] and https://opentelemetry.io/docs/specs/otlp/[OpenTelemetry Protocol (OTLP)^] + +== Quickstart for LangChain users + +If you're using LangChain with OpenTelemetry tracing, you can send traces to Redpanda's `redpanda.otel_traces` glossterm:topic[] to view them in the Transcripts view. + +. Configure LangChain's OpenTelemetry integration by following the https://docs.langchain.com/langsmith/trace-with-opentelemetry[LangChain documentation^]. + +. Deploy a Redpanda Connect pipeline using the `otlp_http` input to receive OTLP traces over HTTP. Create the pipeline in the **Connect** page of your cluster, or see the <> section below for a sample configuration. + +. Configure your OTEL exporter to send traces to your Redpanda Connect pipeline using environment variables: + +[,bash] +---- +# Configure LangChain OTEL integration +export LANGSMITH_OTEL_ENABLED=true +export LANGSMITH_TRACING=true + +# Send traces to Redpanda Connect pipeline +export OTEL_EXPORTER_OTLP_ENDPOINT="https://:4318" +export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer " +---- + +By default, traces are sent to both LangSmith and your Redpanda Connect pipeline. If you want to send traces only to Redpanda (not LangSmith), set: + +[,bash] +---- +export LANGSMITH_OTEL_ONLY="true" +---- + +Your LangChain application will send traces to the `redpanda.otel_traces` topic, making them visible in the Transcripts view in your cluster alongside Remote MCP server and declarative agent traces. + +For non-LangChain applications or custom instrumentation, continue with the sections below. + +== About custom trace ingestion + +Custom agents include applications you build with OpenTelemetry instrumentation that operate independently of Redpanda's Remote MCP servers or declarative agents. Examples include: + +* Custom AI agents built with LangChain, CrewAI, or other frameworks +* Applications with manual OpenTelemetry instrumentation +* Services that integrate with third-party AI platforms + +When these applications send traces to Redpanda's `redpanda.otel_traces` glossterm:topic[], you gain unified observability across all agentic components in your system. Custom agent transcripts appear alongside Remote MCP server and declarative agent transcripts in the Transcripts view, creating xref:ai-agents:observability/concepts.adoc#cross-service-transcripts[cross-service transcripts] that allow you to correlate operations and analyze end-to-end request flows. + +=== Trace format requirements + +Custom agents must emit traces in OTLP format. The `otlp_http` input accepts both OTLP Protobuf (`application/x-protobuf`) and JSON (`application/json`) payloads. For <>, use the `otlp_grpc` input. + +Each trace must follow the OTLP specification with these required fields: + +[cols="1,3", options="header"] +|=== +| Field | Description + +| `traceId` +| Hex-encoded unique identifier for the entire trace + +| `spanId` +| Hex-encoded unique identifier for this span + +| `name` +| Descriptive operation name + +| `startTimeUnixNano` and `endTimeUnixNano` +| Timing information in nanoseconds + +| `instrumentationScope` +| Identifies the library that created the span + +| `status` +| Operation status with code (0 = OK, 2 = ERROR) +|=== + +Optional but recommended fields: +- `parentSpanId` for hierarchical traces +- `attributes` for contextual information + +For complete trace structure details, see xref:ai-agents:observability/concepts.adoc#understand-the-transcript-structure[Understand the transcript structure]. + +== Configure the ingestion pipeline + +Create a Redpanda Connect pipeline that receives HTTP requests containing OTLP traces and publishes them to the `redpanda.otel_traces` topic. The pipeline uses the `otlp_http` input component, which is specifically designed to receive OpenTelemetry Protocol data. + +=== Create the pipeline configuration + +Create a pipeline configuration file that defines the OTLP HTTP ingestion endpoint. + +The `otlp_http` input component: + +* Exposes an OpenTelemetry Collector HTTP receiver +* Accepts traces at the standard `/v1/traces` endpoint +* Listens on port 4318 by default (standard OTLP/HTTP port) +* Converts incoming OTLP data into individual Redpanda OTEL v1 Protobuf messages and publishes them to the `redpanda.otel_traces` topic + +Create a file named `trace-ingestion.yaml`: + +[,yaml] +---- +input: + otlp_http: + address: "0.0.0.0:4318" + auth_token: "${secrets.TRACE_AUTH_TOKEN}" + max_body_size: 4194304 # 4MB default + read_timeout: "10s" + write_timeout: "10s" + +output: + redpanda: + seed_brokers: ["${REDPANDA_BROKERS}"] + topic: "redpanda.otel_traces" + compression: snappy + max_in_flight: 10 +---- + +The `otlp_http` input automatically handles format conversion, so no processors are needed for basic trace ingestion. Each span becomes a separate message in the `redpanda.otel_traces` topic. + +[[use-grpc]] +==== Alternative: Use gRPC instead of HTTP + +If your custom agent requires gRPC transport, use the `otlp_grpc` input instead: + +[,yaml] +---- +input: + otlp_grpc: + address: "0.0.0.0:4317" # Standard OTLP/gRPC port + auth_token: "${secrets.TRACE_AUTH_TOKEN}" + max_recv_msg_size: 4194304 + +output: + redpanda: + seed_brokers: ["${REDPANDA_BROKERS}"] + topic: "redpanda.otel_traces" + compression: snappy + max_in_flight: 10 +---- + +The gRPC input works identically to HTTP but uses Protobuf encoding over gRPC. Clients must include the authentication token in gRPC metadata as `authorization: Bearer `. + +=== Deploy the pipeline in Redpanda Cloud + +. In the *Connect* page of your Redpanda Cloud cluster, click *Create Pipeline*. +. For the input, select the *otlp_http* (or *otlp_grpc*) component. +. Skip to *Add a topic* and select `redpanda.otel_traces` from the list of existing topics. Leave the default advanced settings. +. In the *Add permissions* step, you can create a service account with write access to the `redpanda.otel_traces` topic. +. In the *Create pipeline* step, enter a name for your ingestion pipeline and paste your `trace-ingestion.yaml` configuration. Ensure that you've created the TRACE_AUTH_TOKEN secret you're referencing in the configuration. + +== Send traces from your custom agent + +Configure your custom agent to send OpenTelemetry traces to the ingestion endpoint. The endpoint accepts traces in OTLP format via HTTP on port 4318 at the `/v1/traces` path. + +=== Configure your OTEL exporter + +Install the OpenTelemetry SDK for your language and configure the OTLP exporter to target your Redpanda Connect pipeline endpoint. + +The exporter configuration requires: + +* **Endpoint**: Your pipeline's URL including the `/v1/traces` path +* **Headers**: Authorization header with your bearer token +* **Protocol**: HTTP to match the `otlp_http` input (or gRPC for `otlp_grpc`) + +.Python example for OTLP HTTP exporter +[,python] +---- +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.sdk.resources import Resource + +# Configure resource attributes to identify your agent +resource = Resource(attributes={ + "service.name": "my-custom-agent", + "service.version": "1.0.0" +}) + +# Configure the OTLP HTTP exporter +exporter = OTLPSpanExporter( + endpoint=":4318/v1/traces", + headers={"Authorization": "Bearer YOUR_TOKEN"} +) + +# Set up tracing with batch processing +provider = TracerProvider(resource=resource) +processor = BatchSpanProcessor(exporter) +provider.add_span_processor(processor) +trace.set_tracer_provider(provider) + +# Use the tracer with GenAI semantic conventions +tracer = trace.get_tracer(__name__) +with tracer.start_as_current_span( + "invoke_agent my-assistant", + kind=trace.SpanKind.INTERNAL +) as span: + # Set GenAI semantic convention attributes + span.set_attribute("gen_ai.operation.name", "invoke_agent") + span.set_attribute("gen_ai.agent.name", "my-assistant") + span.set_attribute("gen_ai.provider.name", "openai") + span.set_attribute("gen_ai.request.model", "gpt-4") + + # Your agent logic here + result = process_request() + + # Set token usage if available + span.set_attribute("gen_ai.usage.input_tokens", 150) + span.set_attribute("gen_ai.usage.output_tokens", 75) +---- + +.Node.js example for OTLP HTTP exporter +[,javascript] +---- +const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); +const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); +const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base'); +const { Resource } = require('@opentelemetry/resources'); +const { trace, SpanKind } = require('@opentelemetry/api'); + +// Configure resource +const resource = new Resource({ + 'service.name': 'my-custom-agent', + 'service.version': '1.0.0' +}); + +// Configure OTLP HTTP exporter +const exporter = new OTLPTraceExporter({ + url: 'https://your-pipeline-endpoint.redpanda.cloud:4318/v1/traces', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN' + } +}); + +// Set up provider +const provider = new NodeTracerProvider({ resource }); +provider.addSpanProcessor(new BatchSpanProcessor(exporter)); +provider.register(); + +// Use the tracer with GenAI semantic conventions +const tracer = trace.getTracer('my-agent'); +const span = tracer.startSpan('invoke_agent my-assistant', { + kind: SpanKind.INTERNAL +}); + +// Set GenAI semantic convention attributes +span.setAttribute('gen_ai.operation.name', 'invoke_agent'); +span.setAttribute('gen_ai.agent.name', 'my-assistant'); +span.setAttribute('gen_ai.provider.name', 'openai'); +span.setAttribute('gen_ai.request.model', 'gpt-4'); + +// Your agent logic +processRequest().then(result => { + // Set token usage if available + span.setAttribute('gen_ai.usage.input_tokens', 150); + span.setAttribute('gen_ai.usage.output_tokens', 75); + span.end(); +}); +---- + +TIP: Use environment variables for the endpoint URL and authentication token to keep credentials out of your code. + +=== Use recommended semantic conventions + +The Transcripts view recognizes https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/[OpenTelemetry semantic conventions for GenAI operations^]. Following these conventions ensures your traces display correctly with proper attribution, token usage, and operation identification. + +==== Required attributes for agent operations + +Following the OpenTelemetry semantic conventions, agent spans should include these attributes: + +* Operation identification: +** `gen_ai.operation.name` - Set to `"invoke_agent"` for agent execution spans +** `gen_ai.agent.name` - Human-readable name of your agent (displayed in Transcripts view) +* LLM provider details: +** `gen_ai.provider.name` - LLM provider identifier (e.g., `"openai"`, `"anthropic"`, `"gcp.vertex_ai"`) +** `gen_ai.request.model` - Model name (e.g., `"gpt-4"`, `"claude-sonnet-4"`) +* Token usage (for cost tracking): +** `gen_ai.usage.input_tokens` - Number of input tokens consumed +** `gen_ai.usage.output_tokens` - Number of output tokens generated +* Session correlation: +** `gen_ai.conversation.id` - Identifier linking related agent invocations in the same conversation + +==== Example with semantic conventions + +.Python example with GenAI semantic conventions +[,python] +---- +from opentelemetry import trace + +tracer = trace.get_tracer(__name__) + +# Create an agent invocation span +with tracer.start_as_current_span( + "invoke_agent my-assistant", + kind=trace.SpanKind.INTERNAL +) as span: + # Set required attributes + span.set_attribute("gen_ai.operation.name", "invoke_agent") + span.set_attribute("gen_ai.agent.name", "my-assistant") + span.set_attribute("gen_ai.provider.name", "openai") + span.set_attribute("gen_ai.request.model", "gpt-4") + span.set_attribute("gen_ai.conversation.id", "session-abc-123") + + # Your agent logic here + response = process_agent_request(user_input) + + # Set token usage after completion + span.set_attribute("gen_ai.usage.input_tokens", response.usage.input_tokens) + span.set_attribute("gen_ai.usage.output_tokens", response.usage.output_tokens) +---- + +.Node.js example with GenAI semantic conventions +[,javascript] +---- +const { trace } = require('@opentelemetry/api'); + +const tracer = trace.getTracer('my-agent'); + +const span = tracer.startSpan('invoke_agent my-assistant', { + kind: SpanKind.INTERNAL +}); + +// Set required attributes +span.setAttribute('gen_ai.operation.name', 'invoke_agent'); +span.setAttribute('gen_ai.agent.name', 'my-assistant'); +span.setAttribute('gen_ai.provider.name', 'openai'); +span.setAttribute('gen_ai.request.model', 'gpt-4'); +span.setAttribute('gen_ai.conversation.id', 'session-abc-123'); + +// Your agent logic +const response = await processAgentRequest(userInput); + +// Set token usage +span.setAttribute('gen_ai.usage.input_tokens', response.usage.inputTokens); +span.setAttribute('gen_ai.usage.output_tokens', response.usage.outputTokens); + +span.end(); +---- + +=== Validate trace format + +Before deploying to production, verify your traces match the expected format. + +//// + +* How to validate trace format against schema +* Common format issues and solutions +* Tools for format validation +==== + +//// + +Test your agent locally and inspect the traces it produces: + +[,bash] +---- +# Example validation steps + +---- + +== Verify trace ingestion + +After deploying your pipeline and configuring your custom agent, verify traces are flowing correctly. + +=== Consume traces from the topic + +Check that traces are being published to the `redpanda.otel_traces` topic: + +[,bash] +---- +rpk topic consume redpanda.otel_traces --offset end -n 10 +---- + +You can also view the `redpanda.otel_traces` topic in the *Topics* page of Redpanda Cloud UI. + +Look for spans with your custom `instrumentationScope.name` to identify traces from your agent. + +=== View traces in Transcripts + +After your custom agent sends traces through the pipeline, they appear in your cluster's *Agentic AI > Transcripts* view alongside traces from Remote MCP servers and declarative agents. + +==== Identify custom agent transcripts + +Custom agent transcripts are identified by the `service.name` resource attribute, which differs from Redpanda's built-in services (`ai-agent` for declarative agents, `mcp-{server-id}` for MCP servers). See xref:ai-agents:observability/concepts.adoc#cross-service-transcripts[Cross-service transcripts] to understand how the `service.name` attribute identifies transcript sources. + +Your custom agent transcripts display with: + +* **Service name** in the service filter dropdown (from your `service.name` resource attribute) +* **Agent name** in span details (from the `gen_ai.agent.name` attribute) +* **Operation names** like `"invoke_agent my-assistant"` indicating agent executions + +For detailed instructions on filtering, searching, and navigating transcripts in the UI, see xref:ai-agents:observability/view-transcripts.adoc[View Transcripts]. + +==== Token usage tracking + +If your spans include the recommended token usage attributes (`gen_ai.usage.input_tokens` and `gen_ai.usage.output_tokens`), they display in the summary panel's token usage section. This enables cost tracking alongside Remote MCP server and declarative agent transcripts. + +== Troubleshooting + +//// +* Common issues and solutions +* How to monitor pipeline health +* Log locations and debugging techniques +* Failure modes and diagnostics + +//// + +=== Pipeline not receiving requests + +If your custom agent cannot reach the ingestion endpoint: + +. Verify the endpoint URL includes the correct port and path: + * HTTP: `https://your-endpoint:4318/v1/traces` + * gRPC: `https://your-endpoint:4317` +. Check network connectivity and firewall rules. +. Ensure authentication tokens are valid and properly formatted in the `Authorization: Bearer ` header (HTTP) or `authorization` metadata field (gRPC). +. Verify the Content-Type header matches your data format (`application/x-protobuf` or `application/json`). +. Review pipeline logs for connection errors or authentication failures. + +=== Traces not appearing in topic + +If requests succeed but traces do not appear in `redpanda.otel_traces`: + +. Check pipeline output configuration. +. Verify topic permissions. +. Validate trace format matches OTLP specification. + +== Limitations + +* The `otlp_http` and `otlp_grpc` inputs accept only traces, logs, and metrics, not profiles. +* Only traces are published to the `redpanda.otel_traces` topic. +* Exceeded rate limits return HTTP 429 (HTTP) or ResourceExhausted status (gRPC). + +== Next steps + +* xref:ai-agents:observability/view-transcripts.adoc[] +* xref:ai-agents:agents/monitor-agents.adoc[Observability for declarative agents] +* https://docs.redpanda.com/redpanda-connect/components/inputs/otlp_http/[OTLP HTTP input reference^] - Complete configuration options for the `otlp_http` component +* https://docs.redpanda.com/redpanda-connect/components/inputs/otlp_grpc/[OTLP gRPC input reference^] - Alternative gRPC-based trace ingestion From 9592dcb56523afa36fe2b00d7c10d33b19c2eb7b Mon Sep 17 00:00:00 2001 From: Kat Batuigas Date: Mon, 2 Feb 2026 15:33:14 -0800 Subject: [PATCH 89/97] Add byoa telemetry to nav tree --- modules/ROOT/nav.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 489026742..b592ffa5d 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -94,6 +94,7 @@ ** xref:ai-agents:observability/index.adoc[Transcripts] *** xref:ai-agents:observability/concepts.adoc[Concepts] *** xref:ai-agents:observability/view-transcripts.adoc[View Transcripts] +*** xref:ai-agents:observability/ingest-custom-traces.adoc[Ingest Traces from Custom Agents] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] From ca6f6dc8f75f8e73b94717e476ab5802ac36810e Mon Sep 17 00:00:00 2001 From: micheleRP Date: Mon, 2 Feb 2026 22:34:21 -0700 Subject: [PATCH 90/97] conditionalize out content not for package 1 --- modules/ROOT/nav.adoc | 8 ++++---- .../ai-agents/pages/ai-gateway/admin/setup-guide.adoc | 6 ++---- .../pages/ai-gateway/cel-routing-cookbook.adoc | 1 - .../pages/ai-gateway/gateway-architecture.adoc | 1 - .../pages/ai-gateway/gateway-quickstart.adoc | 2 -- .../ai-gateway/integrations/claude-code-admin.adoc | 5 +---- .../ai-gateway/integrations/claude-code-user.adoc | 1 - .../pages/ai-gateway/integrations/cline-admin.adoc | 5 +---- .../pages/ai-gateway/integrations/cline-user.adoc | 1 - .../pages/ai-gateway/integrations/continue-admin.adoc | 5 +---- .../pages/ai-gateway/integrations/continue-user.adoc | 1 - .../pages/ai-gateway/integrations/cursor-admin.adoc | 5 +---- .../pages/ai-gateway/integrations/cursor-user.adoc | 1 - .../ai-gateway/integrations/github-copilot-admin.adoc | 7 ++----- .../ai-gateway/integrations/github-copilot-user.adoc | 1 - .../pages/ai-gateway/mcp-aggregation-guide.adoc | 2 -- .../pages/ai-gateway/what-is-ai-gateway.adoc | 11 +++++++---- .../ai-gateway => partials}/migration-guide.adoc | 0 .../ai-gateway => partials}/observability-logs.adoc | 0 .../observability-metrics.adoc | 0 20 files changed, 19 insertions(+), 44 deletions(-) rename modules/ai-agents/{pages/ai-gateway => partials}/migration-guide.adoc (100%) rename modules/ai-agents/{pages/ai-gateway => partials}/observability-logs.adoc (100%) rename modules/ai-agents/{pages/ai-gateway => partials}/observability-metrics.adoc (100%) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index fc1b92a15..05771a00c 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -35,10 +35,10 @@ **** xref:ai-agents:ai-gateway/builders/connect-your-agent.adoc[Connect Your Agent] **** xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[CEL Routing Patterns] **** xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[MCP Aggregation] -*** Observability -**** xref:ai-agents:ai-gateway/observability-logs.adoc[Request Logs] -**** xref:ai-agents:ai-gateway/observability-metrics.adoc[Metrics and Analytics] -*** xref:ai-agents:ai-gateway/migration-guide.adoc[Migrate] +//*** Observability +//**** xref:ai-agents:ai-gateway/observability-logs.adoc[Request Logs] +//**** xref:ai-agents:ai-gateway/observability-metrics.adoc[Metrics and Analytics] +//*** xref:ai-agents:ai-gateway/migration-guide.adoc[Migrate] *** xref:ai-agents:ai-gateway/integrations/index.adoc[Integrations] **** Claude Code ***** xref:ai-agents:ai-gateway/integrations/claude-code-admin.adoc[Admin Guide] diff --git a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc index 0a0a559c7..c3e20e1b2 100644 --- a/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/admin/setup-guide.adoc @@ -316,10 +316,8 @@ Users can then discover and connect to the gateway using the information provide * xref:ai-gateway/cel-routing-cookbook.adoc[CEL Routing Cookbook] - Advanced routing patterns // * xref:ai-gateway/admin/networking-configuration.adoc[Networking Configuration] - Configure private endpoints and connectivity -*Monitor and observe:* - -* xref:ai-gateway/observability-metrics.adoc[Monitor Usage] - Track costs and usage across all gateways -* xref:ai-gateway/observability-logs.adoc[Request Logs] - View and filter request logs +//*Monitor and observe:* +// *Integrate tools:* diff --git a/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc index 0379595ee..57d997342 100644 --- a/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc +++ b/modules/ai-agents/pages/ai-gateway/cel-routing-cookbook.adoc @@ -951,4 +951,3 @@ Each request evaluates CEL expression once. Total latency impact: == Next steps * *Apply CEL routing*: See the gateway configuration options available in the Redpanda Cloud console. -* *Monitor routing decisions*: xref:ai-agents:ai-gateway/observability-logs.adoc[] diff --git a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc index a25c0d579..5295f2134 100644 --- a/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc +++ b/modules/ai-agents/pages/ai-gateway/gateway-architecture.adoc @@ -147,4 +147,3 @@ The gateway only loads and exposes specific tools when requested, which dramatic * xref:ai-agents:ai-gateway/gateway-quickstart.adoc[]: Route your first request through AI Gateway * xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure MCP server aggregation for AI agents -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor request logs, token usage, and costs diff --git a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc index 59292fa1a..ffcf58aeb 100644 --- a/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc +++ b/modules/ai-agents/pages/ai-gateway/gateway-quickstart.adoc @@ -561,8 +561,6 @@ Explore advanced AI Gateway features: * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Advanced CEL routing patterns for traffic distribution and cost optimization * xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure MCP server aggregation and deferred tool loading -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor request logs, token usage, and costs -* xref:ai-agents:ai-gateway/migration-guide.adoc[]: Migrate existing LLM integrations to AI Gateway * xref:ai-agents:ai-gateway/integrations/index.adoc[]: Connect more AI development tools Learn about the architecture: diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc index 5bbb2e844..08ab8c01d 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-admin.adoc @@ -299,7 +299,7 @@ Implement token rotation for security: . Create a new token before the existing token expires . Distribute the new token to users -. Monitor usage of the old token in xref:ai-agents:ai-gateway/observability-logs.adoc[request logs] +. Monitor usage of the old token in (observability dashboard) . Revoke the old token after all users have migrated == Configure Claude Code clients @@ -393,7 +393,6 @@ Track Claude Code activity through gateway observability features. |Identify failing requests or misconfigured clients |=== -For detailed metrics configuration, see xref:ai-agents:ai-gateway/observability-metrics.adoc[]. === Query logs via API @@ -498,7 +497,5 @@ Causes and solutions: == Next steps -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Analyze detailed request logs -* xref:ai-agents:ai-gateway/observability-metrics.adoc[]: Set up metrics dashboards * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Implement advanced routing rules * xref:ai-agents:mcp/remote/overview.adoc[]: Deploy Remote MCP servers for custom tools diff --git a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc index 7fb085124..3cc98eae8 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/claude-code-user.adoc @@ -413,7 +413,6 @@ chmod 600 ~/.claude.json == Next steps * xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure deferred tool loading to reduce token costs -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor Claude Code requests in the gateway dashboard * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Use CEL expressions to route Claude Code requests based on context == Related pages diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc index 8552199c4..3092e84e2 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-admin.adoc @@ -320,7 +320,7 @@ Implement token rotation for security: . Create a new token before the existing token expires . Distribute the new token to users -. Monitor usage of the old token in xref:ai-agents:ai-gateway/observability-logs.adoc[request logs] +. Monitor usage of the old token in (observability dashboard) . Revoke the old token after all users have migrated == Configure Cline clients @@ -447,7 +447,6 @@ Cline autonomous operations may generate request sequences. Look for patterns to |Identify failing requests or misconfigured clients |=== -For detailed metrics configuration, see xref:ai-agents:ai-gateway/observability-metrics.adoc[]. === Query logs via API @@ -584,7 +583,5 @@ Causes and solutions: == Next steps -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Analyze detailed request logs -* xref:ai-agents:ai-gateway/observability-metrics.adoc[]: Set up metrics dashboards * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Implement advanced routing rules * xref:ai-agents:mcp/remote/overview.adoc[]: Deploy Remote MCP servers for custom tools diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc index cc66ca5d7..795a638eb 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cline-user.adoc @@ -752,7 +752,6 @@ The gateway automatically blocks requests that would exceed the limit. == Next steps * xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure deferred tool loading to reduce token costs -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor Cline requests in the gateway dashboard * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Use CEL expressions to route Cline requests based on task complexity == Related pages diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc index ce75b6306..6e160c87c 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-admin.adoc @@ -412,7 +412,7 @@ Implement token rotation for security: . Create a new token before the existing token expires . Distribute the new token to users -. Monitor usage of the old token in xref:ai-agents:ai-gateway/observability-logs.adoc[request logs] +. Monitor usage of the old token in (observability dashboard) . Revoke the old token after all users have migrated == Configure Continue.dev clients @@ -600,7 +600,6 @@ Continue.dev generates different request patterns: |Identify failing providers or misconfigured backends |=== -For detailed metrics configuration, see xref:ai-agents:ai-gateway/observability-metrics.adoc[]. === Query logs via API @@ -757,7 +756,5 @@ This is expected behavior, not a configuration issue: == Next steps -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Analyze detailed request logs -* xref:ai-agents:ai-gateway/observability-metrics.adoc[]: Set up metrics dashboards * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Implement advanced routing rules * xref:ai-agents:mcp/remote/overview.adoc[]: Deploy Remote MCP servers for custom tools diff --git a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc index 5dcb3094c..b8f282021 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/continue-user.adoc @@ -932,7 +932,6 @@ Autocomplete rarely needs more than 256 tokens, while chat responses can vary. == Next steps * xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure deferred tool loading to reduce token costs -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor Continue.dev requests in the gateway dashboard * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Use CEL expressions to route Continue.dev requests based on context == Related pages diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc index 55f305500..cfaa68595 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-admin.adoc @@ -373,7 +373,7 @@ Implement token rotation for security: . Create a new token before the existing token expires . Distribute the new token to users -. Monitor usage of the old token in xref:ai-agents:ai-gateway/observability-logs.adoc[request logs] +. Monitor usage of the old token in (observability dashboard) . Revoke the old token after all users have migrated == Multi-tenant deployment strategies @@ -647,7 +647,6 @@ Cursor generates different request patterns: |Monitor OpenAI-to-provider format conversion success |=== -For detailed metrics configuration, see xref:ai-agents:ai-gateway/observability-metrics.adoc[]. === Query logs via API @@ -811,7 +810,5 @@ Causes and solutions: == Next steps -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Analyze detailed request logs and transform operations -* xref:ai-agents:ai-gateway/observability-metrics.adoc[]: Set up metrics dashboards * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Implement advanced routing rules for model prefix routing * xref:ai-agents:mcp/remote/overview.adoc[]: Deploy Remote MCP servers for custom tools diff --git a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc index 45041ea5d..cd7dda2b1 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/cursor-user.adoc @@ -869,7 +869,6 @@ This sends only search + orchestrator tools initially, reducing token usage sign == Next steps * xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Configure deferred tool loading to work within Cursor's 40-tool limit -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor Cursor requests in the gateway dashboard * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Use CEL expressions to route Cursor requests based on context == Related pages diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc index fb2cf320f..730d95498 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-admin.adoc @@ -321,7 +321,7 @@ Implement token rotation for security: . Create a new token before the existing token expires . Update organization-level GitHub Copilot configuration with the new token -. Monitor usage of the old token in xref:ai-agents:ai-gateway/observability-logs.adoc[request logs] +. Monitor usage of the old token in (observability dashboard) . Revoke the old token after the configuration update propagates == Multi-tenant deployment strategies @@ -647,7 +647,6 @@ GitHub Copilot generates distinct request patterns: |Track usage by team (if using multi-tenant strategies) |=== -For detailed metrics configuration, see xref:ai-agents:ai-gateway/observability-metrics.adoc[]. === Query logs via API @@ -824,7 +823,5 @@ Causes and solutions: == Next steps -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Analyze detailed request logs and transform operations -* xref:ai-agents:ai-gateway/observability-metrics.adoc[]: Set up metrics dashboards * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Implement advanced routing rules for model aliasing -* xref:ai-agents:ai-gateway/migration-guide.adoc[]: Migrate from direct provider access to AI Gateway + diff --git a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc index b52f79d3b..c8b23edb2 100644 --- a/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc +++ b/modules/ai-agents/pages/ai-gateway/integrations/github-copilot-user.adoc @@ -953,7 +953,6 @@ Generate project-specific cost reports from the gateway dashboard. == Next steps -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor GitHub Copilot requests in the gateway dashboard * xref:ai-agents:ai-gateway/cel-routing-cookbook.adoc[]: Use CEL expressions to route Copilot requests based on context * xref:ai-agents:ai-gateway/mcp-aggregation-guide.adoc[]: Learn about MCP tool integration (if using Copilot Workspace) diff --git a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc index a5367ad38..68ba38cb0 100644 --- a/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc +++ b/modules/ai-agents/pages/ai-gateway/mcp-aggregation-guide.adoc @@ -1024,5 +1024,3 @@ response = agent.run("Find all premium users in the database") == Next steps -* xref:ai-agents:ai-gateway/observability-logs.adoc[]: Monitor MCP usage in request logs. -* xref:ai-agents:ai-gateway/observability-metrics.adoc[]: Track MCP metrics and costs. \ No newline at end of file diff --git a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc index 62e60a4d3..6af235d8a 100644 --- a/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc +++ b/modules/ai-agents/pages/ai-gateway/what-is-ai-gateway.adoc @@ -26,7 +26,7 @@ Finally, observability is fragmented across provider dashboards. You cannot reco Redpanda AI Gateway addresses these challenges through four core capabilities: -=== 1. Unified LLM access (single endpoint for all providers) +=== Unified LLM access (single endpoint for all providers) AI Gateway provides a single OpenAI-compatible endpoint that routes requests to multiple LLM providers. Instead of integrating with each provider's SDK separately, you configure your application once and switch providers by changing only the model parameter. @@ -80,7 +80,7 @@ response = client.chat.completions.create( To switch providers, you change only the `model` parameter from `openai/gpt-4o` to `anthropic/claude-sonnet-3.5`. No code changes or redeployment needed. -=== 2. Policy-based routing and cost control +=== Policy-based routing and cost control AI Gateway lets you define routing rules, rate limits, and budgets once, then enforces them automatically for all requests. @@ -98,7 +98,7 @@ You can also set different rate limits and spend limits per environment to preve For reliability, you can configure provider pools with automatic failover. If you configure OpenAI GPT-4 as your primary model and Anthropic Claude Opus as the fallback, the gateway automatically routes requests to the fallback when it detects rate limits or timeouts from the primary provider. This configuration can significantly improve uptime (potentially up to 99.9% in some configurations) even during provider outages. -=== 3. MCP aggregation and orchestration +=== MCP aggregation and orchestration AI Gateway aggregates multiple MCP (Model Context Protocol) servers and provides deferred tool loading, which dramatically reduces token costs for AI agents. @@ -108,7 +108,7 @@ With AI Gateway, you configure approved MCP servers once, and the gateway loads For complex workflows, AI Gateway provides a JavaScript-based orchestrator tool that reduces multi-step workflows from multiple round trips to a single call. For example, you can create a workflow that searches a vector database and, if the results are insufficient, falls back to web search—all in one orchestration step. -=== 4. Unified observability and cost tracking +=== Unified observability and cost tracking AI Gateway provides a single dashboard that tracks all LLM traffic across providers, eliminating the need to switch between multiple provider dashboards. @@ -116,6 +116,7 @@ The dashboard tracks request volume per gateway, model, and provider, along with This unified view helps you answer critical questions such as which model is the most cost-effective for your use case, why a specific user request failed, how much your staging environment costs per week, and what the latency difference is between providers for your workload. +ifdef::show-gateway-patterns[] == Common gateway patterns === Team isolation @@ -145,6 +146,8 @@ request.headers["x-customer-tier"] == "pro" ? "anthropic/claude-sonnet-3.5" : "anthropic/claude-haiku" ---- +endif::[] + == When to use AI Gateway AI Gateway is ideal for organizations that: diff --git a/modules/ai-agents/pages/ai-gateway/migration-guide.adoc b/modules/ai-agents/partials/migration-guide.adoc similarity index 100% rename from modules/ai-agents/pages/ai-gateway/migration-guide.adoc rename to modules/ai-agents/partials/migration-guide.adoc diff --git a/modules/ai-agents/pages/ai-gateway/observability-logs.adoc b/modules/ai-agents/partials/observability-logs.adoc similarity index 100% rename from modules/ai-agents/pages/ai-gateway/observability-logs.adoc rename to modules/ai-agents/partials/observability-logs.adoc diff --git a/modules/ai-agents/pages/ai-gateway/observability-metrics.adoc b/modules/ai-agents/partials/observability-metrics.adoc similarity index 100% rename from modules/ai-agents/pages/ai-gateway/observability-metrics.adoc rename to modules/ai-agents/partials/observability-metrics.adoc From 723eaf4bc063add317dabf60274d68f6d679b60d Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 3 Feb 2026 09:11:16 +0000 Subject: [PATCH 91/97] Simplify examples --- .../transaction-dispute-resolution.adoc | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index b4eb3670f..7ed31dd5e 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -341,15 +341,7 @@ I see a $1,847.99 charge from 'LUXURY WATCHES INT' in Singapore on transaction T Watch the conversation panel as the investigation progresses. You'll see the root agent call each sub-agent in sequence. After all sub-agents complete (30-90 seconds), the agent sends its final response to the chat. -In the conversation panel, you'll see the root agent: - -. Routes to account-agent and retrieves customer location data and spending patterns -. Routes to fraud-agent and calculates critical risk level (95+ score) -. Routes to merchant-agent and confirms merchant legitimacy issues -. Routes to compliance-agent and logs investigation -. Takes immediate action and blocks card and approves dispute claim - -After all sub-agents complete, the agent sends its final response to the chat. +The final response should clearly state the transaction is fraudulent, summarize findings from each sub-agent, and provide a list of actions the agent is going to take. This flow demonstrates multi-agent coordination for high-confidence fraud decisions with realistic banking communication. @@ -364,15 +356,7 @@ Click *Clear context*. Then enter: I see three $29.99 charges from 'EXAMPLE STREAMING' last month, but I only subscribed once. My customer ID is CUST-1002 and one of the transactions is TXN-89014. ---- -Watch the conversation panel as the agent investigates. After the sub-agent calls complete, the agent should send a response with a realistic escalation pattern: - -In the conversation panel, you'll see the root agent: - -. Routes to account-agent and confirms recurring charges -. Routes to fraud-agent and receives moderate risk score (not clear fraud) -. Routes to merchant-agent and confirms legitimate merchant -. Routes to compliance-agent and logs as billing error dispute -. Escalates to human specialist (conflicting evidence, requires merchant subscription records) +Watch the conversation panel as the agent investigates. After the sub-agent calls complete, the agent should send a response with a realistic escalation. This demonstrates the escalation pattern when evidence is ambiguous and requires human review. From 40c87e586ccc1dd2d1691db5d2a9e8d14cc96da8 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 3 Feb 2026 09:16:12 +0000 Subject: [PATCH 92/97] Fix label --- .../pages/agents/tutorials/transaction-dispute-resolution.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index 7ed31dd5e..23e460909 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -456,7 +456,7 @@ The pipeline needs SASL credentials to read from and write to Redpanda topics. . Click *Create*. -. Click *Create ACL* to grant permissions. +. Click *Create ACLs* to grant permissions. . Click the *Clusters* tab for cluster permissions and select *Allow all*. From bd2797df5f88a8f93fc4647b84751c1442aaf5fc Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 3 Feb 2026 09:17:49 +0000 Subject: [PATCH 93/97] Fix label --- .../pages/agents/tutorials/transaction-dispute-resolution.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index 23e460909..5685d4006 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -490,7 +490,7 @@ The pipeline needs SASL credentials stored as secrets to authenticate with Redpa === Create the pipeline . Go to *Connect* in the Redpanda Cloud Console. -. Click *Create Pipeline*. +. Click *Create a pipeline*. . In the numbered steps, click *4 Add permissions*. . Select *Service Account*. + From 4b67d298fae8e42093d338545205847fea75b1e5 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 3 Feb 2026 09:19:01 +0000 Subject: [PATCH 94/97] Fix label --- .../pages/agents/tutorials/transaction-dispute-resolution.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc index 5685d4006..b36c37cf8 100644 --- a/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc +++ b/modules/ai-agents/pages/agents/tutorials/transaction-dispute-resolution.adoc @@ -498,7 +498,7 @@ The Service Account is required for the `a2a_message` processor to authenticate . Click *Next*. . Name the pipeline `dispute-pipeline`. -. Paste this configuration: +. Paste this configuration and click *Create Pipeline*: + [,yaml,role="no-placeholders"] ---- From 81da213af3565895a45ca9f693f3b08c450d5d64 Mon Sep 17 00:00:00 2001 From: Paulo Borges Date: Tue, 3 Feb 2026 12:16:07 -0300 Subject: [PATCH 95/97] force build --- modules/ROOT/nav.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index b592ffa5d..bbd44b9b5 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -94,7 +94,7 @@ ** xref:ai-agents:observability/index.adoc[Transcripts] *** xref:ai-agents:observability/concepts.adoc[Concepts] *** xref:ai-agents:observability/view-transcripts.adoc[View Transcripts] -*** xref:ai-agents:observability/ingest-custom-traces.adoc[Ingest Traces from Custom Agents] +*** xref:ai-agents:observability/ingest-custom-traces.adoc[Ingest Traces from Custom Agents] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] From 30709dc28ecec1fbf277b7433fc7cb1a85f375ac Mon Sep 17 00:00:00 2001 From: micheleRP Date: Tue, 3 Feb 2026 08:32:57 -0700 Subject: [PATCH 96/97] update nav.adoc --- modules/ROOT/nav.adoc | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 05771a00c..2a14d6691 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -24,6 +24,25 @@ ** xref:get-started:cluster-types/create-dedicated-cloud-cluster.adoc[] * xref:ai-agents:index.adoc[Agentic AI] +** xref:ai-agents:mcp/index.adoc[MCP] +*** xref:ai-agents:mcp/overview.adoc[MCP Overview] +*** xref:ai-agents:mcp/remote/index.adoc[Remote MCP] +**** xref:ai-agents:mcp/remote/overview.adoc[Overview] +**** xref:ai-agents:mcp/remote/quickstart.adoc[Quickstart] +**** xref:ai-agents:mcp/remote/concepts.adoc[Concepts] +**** xref:ai-agents:mcp/remote/create-tool.adoc[Create a Tool] +**** xref:ai-agents:mcp/remote/best-practices.adoc[Best Practices] +**** xref:ai-agents:mcp/remote/tool-patterns.adoc[Tool Patterns] +**** xref:ai-agents:mcp/remote/troubleshooting.adoc[Troubleshooting] +**** xref:ai-agents:mcp/remote/admin-guide.adoc[Admin Guide] +***** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] +***** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] +***** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] +**** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] +*** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] +**** xref:ai-agents:mcp/local/overview.adoc[Overview] +**** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] +**** xref:ai-agents:mcp/local/configuration.adoc[Configure] ** xref:ai-agents:ai-gateway/index.adoc[AI Gateway] *** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[Overview] *** xref:ai-agents:ai-gateway/gateway-quickstart.adoc[Quickstart] @@ -55,25 +74,6 @@ **** GitHub Copilot ***** xref:ai-agents:ai-gateway/integrations/github-copilot-admin.adoc[Admin Guide] ***** xref:ai-agents:ai-gateway/integrations/github-copilot-user.adoc[User Guide] -** xref:ai-agents:mcp/index.adoc[MCP] -*** xref:ai-agents:mcp/overview.adoc[MCP Overview] -*** xref:ai-agents:mcp/remote/index.adoc[Remote MCP] -**** xref:ai-agents:mcp/remote/overview.adoc[Overview] -**** xref:ai-agents:mcp/remote/quickstart.adoc[Quickstart] -**** xref:ai-agents:mcp/remote/concepts.adoc[Concepts] -**** xref:ai-agents:mcp/remote/create-tool.adoc[Create a Tool] -**** xref:ai-agents:mcp/remote/best-practices.adoc[Best Practices] -**** xref:ai-agents:mcp/remote/tool-patterns.adoc[Tool Patterns] -**** xref:ai-agents:mcp/remote/troubleshooting.adoc[Troubleshooting] -**** xref:ai-agents:mcp/remote/admin-guide.adoc[Admin Guide] -***** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] -***** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] -***** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] -**** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] -*** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] -**** xref:ai-agents:mcp/local/overview.adoc[Overview] -**** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] -**** xref:ai-agents:mcp/local/configuration.adoc[Configure] * xref:develop:connect/about.adoc[Redpanda Connect] ** xref:develop:connect/connect-quickstart.adoc[Quickstart] From 2261b3edd3f30ee61ec0cb7df429da72e962fb4a Mon Sep 17 00:00:00 2001 From: Paulo Borges Date: Tue, 3 Feb 2026 16:49:05 -0300 Subject: [PATCH 97/97] fix nav --- modules/ROOT/nav.adoc | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index b3b6993c8..fe1ee293c 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -25,24 +25,43 @@ * xref:ai-agents:index.adoc[Agentic AI] ** xref:ai-agents:mcp/index.adoc[MCP] -*** xref:ai-agents:mcp/overview.adoc[MCP Overview] +*** xref:ai-agents:mcp/overview.adoc[Overview] *** xref:ai-agents:mcp/remote/index.adoc[Remote MCP] **** xref:ai-agents:mcp/remote/overview.adoc[Overview] -**** xref:ai-agents:mcp/remote/quickstart.adoc[Quickstart] **** xref:ai-agents:mcp/remote/concepts.adoc[Concepts] +**** xref:ai-agents:mcp/remote/quickstart.adoc[Quickstart] **** xref:ai-agents:mcp/remote/create-tool.adoc[Create a Tool] **** xref:ai-agents:mcp/remote/best-practices.adoc[Best Practices] **** xref:ai-agents:mcp/remote/tool-patterns.adoc[Tool Patterns] -**** xref:ai-agents:mcp/remote/troubleshooting.adoc[Troubleshooting] -**** xref:ai-agents:mcp/remote/admin-guide.adoc[Admin Guide] -***** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] -***** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] -***** xref:ai-agents:mcp/remote/monitor-activity.adoc[Monitor Activity] -**** xref:ai-agents:mcp/remote/pipeline-patterns.adoc[MCP Server Patterns] +**** xref:ai-agents:mcp/remote/troubleshooting.adoc[Troubleshoot] +**** xref:ai-agents:mcp/remote/manage-servers.adoc[Manage Servers] +**** xref:ai-agents:mcp/remote/monitor-mcp-servers.adoc[Monitor MCP Servers] +**** xref:ai-agents:mcp/remote/scale-resources.adoc[Scale Resources] *** xref:ai-agents:mcp/local/index.adoc[Redpanda Cloud Management MCP Server] **** xref:ai-agents:mcp/local/overview.adoc[Overview] **** xref:ai-agents:mcp/local/quickstart.adoc[Quickstart] **** xref:ai-agents:mcp/local/configuration.adoc[Configure] +** xref:ai-agents:agents/index.adoc[Agents] +*** xref:ai-agents:agents/get-started-index.adoc[Get Started] +**** xref:ai-agents:agents/overview.adoc[Overview] +**** xref:ai-agents:agents/concepts.adoc[Concepts] +**** xref:ai-agents:agents/quickstart.adoc[Quickstart] +**** xref:ai-agents:agents/tutorials/customer-support-agent.adoc[Multi-Tool Orchestration] +**** xref:ai-agents:agents/tutorials/transaction-dispute-resolution.adoc[Multi-Agent Systems] +*** xref:ai-agents:agents/build-index.adoc[Build Agents] +**** xref:ai-agents:agents/create-agent.adoc[Create an Agent] +**** xref:ai-agents:agents/prompt-best-practices.adoc[System Prompt Best Practices] +**** xref:ai-agents:agents/architecture-patterns.adoc[Architecture Patterns] +**** xref:ai-agents:agents/troubleshooting.adoc[Troubleshoot] +*** xref:ai-agents:agents/monitor-agents.adoc[Monitor Agents] +*** xref:ai-agents:agents/integration-index.adoc[Agent Integrations] +**** xref:ai-agents:agents/integration-overview.adoc[Integration Patterns] +**** xref:ai-agents:agents/pipeline-integration-patterns.adoc[Pipeline to Agent] +**** xref:ai-agents:agents/a2a-concepts.adoc[A2A Protocol] +** xref:ai-agents:observability/index.adoc[Transcripts] +*** xref:ai-agents:observability/concepts.adoc[Concepts] +*** xref:ai-agents:observability/view-transcripts.adoc[View Transcripts] +*** xref:ai-agents:observability/ingest-custom-traces.adoc[Ingest Traces from Custom Agents] ** xref:ai-agents:ai-gateway/index.adoc[AI Gateway] *** xref:ai-agents:ai-gateway/what-is-ai-gateway.adoc[Overview] *** xref:ai-agents:ai-gateway/gateway-quickstart.adoc[Quickstart]