Skip to content

Commit a4dfbfe

Browse files
committed
Update langgraph single agent requirements.txt for security scan reasons
2 parents 94fd818 + d125016 commit a4dfbfe

46 files changed

Lines changed: 1180 additions & 532 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/AGUI_INTEGRATION.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# AG-UI Protocol Integration
2+
3+
Guide for using the AG-UI (Agent-User Interaction) protocol patterns in FAST.
4+
5+
---
6+
7+
## Overview
8+
9+
[AG-UI](https://docs.ag-ui.com/concepts/overview) is an open protocol that defines a standard SSE event format for agent-to-frontend communication. Instead of each framework emitting its own event schema (Strands events, LangChain message chunks, etc.), AG-UI provides a unified event vocabulary: `TEXT_MESSAGE_CONTENT`, `TOOL_CALL_START`, `TOOL_CALL_ARGS`, `TOOL_CALL_RESULT`, `RUN_FINISHED`, and so on.
10+
11+
FAST includes two AG-UI agent patterns:
12+
13+
| Pattern | Framework | Location |
14+
|---------|-----------|----------|
15+
| `agui-strands-agent` | Strands + `ag-ui-strands` | `agent_patterns/agui-strands-agent/` |
16+
| `agui-langgraph-agent` | LangGraph + `copilotkit` | `agent_patterns/agui-langgraph-agent/` |
17+
18+
Both patterns use `BedrockAgentCoreApp` as the entrypoint (same as the HTTP patterns), which means AgentCore Runtime headers (WorkloadAccessToken, Authorization, Session-Id) are available for Gateway auth, Memory, and secure user identity extraction.
19+
20+
---
21+
22+
## How It Works
23+
24+
### Architecture
25+
26+
```
27+
Frontend (Amplify)
28+
29+
│ POST /invocations (AG-UI RunAgentInput payload)
30+
31+
AgentCore Runtime
32+
33+
│ Proxies request to container port 8080
34+
│ Injects: WorkloadAccessToken, Authorization, Session-Id headers
35+
36+
Agent Container
37+
38+
│ BedrockAgentCoreApp reads headers → sets ContextVars
39+
│ @entrypoint handler creates agent, runs it
40+
41+
AG-UI Wrapper (StrandsAgent / LangGraphAGUIAgent)
42+
43+
│ Translates framework events → AG-UI SSE events
44+
45+
Frontend Parser (parsers/agui.ts)
46+
47+
│ Maps AG-UI events → StreamEvent types
48+
49+
ChatInterface.tsx renders messages
50+
```
51+
52+
### Request Flow
53+
54+
1. The frontend sends an AG-UI `RunAgentInput` payload (with `threadId`, `messages`, `runId`)
55+
2. AgentCore Runtime proxies the request, injecting auth headers
56+
3. `BedrockAgentCoreApp` reads headers and populates `BedrockAgentCoreContext` (ContextVars)
57+
4. The `@entrypoint` handler extracts user identity from the JWT, creates the agent with Memory and Gateway tools
58+
5. The AG-UI wrapper translates framework streaming events into AG-UI SSE events
59+
6. The frontend `parseAguiChunk` parser maps AG-UI events to the shared `StreamEvent` types
60+
61+
### AG-UI vs HTTP Protocol on AgentCore Runtime
62+
63+
AgentCore Runtime supports both `HTTP` and `AGUI` server protocols. The difference is minimal: with `AGUI`, platform-level errors are returned as AG-UI-compliant `RUN_ERROR` events in the SSE stream (HTTP 200) instead of HTTP error codes. Everything else — auth, session headers, payload passthrough — is identical.
64+
65+
The AG-UI patterns in FAST deploy with `HTTP` protocol, which works correctly because the agent container handles AG-UI event formatting internally.
66+
67+
---
68+
69+
## Agent Patterns
70+
71+
### AG-UI Strands (`agui-strands-agent`)
72+
73+
**Location**: `agent_patterns/agui-strands-agent/`
74+
75+
Uses `ag-ui-strands` (`StrandsAgent`) to wrap a Strands `Agent`. The agent is created per-request inside the `@entrypoint` handler, ensuring each request gets a fresh `Agent` with the correct `session_manager` and fresh MCP client connections.
76+
77+
**Includes**: AgentCore Memory, Gateway MCP tools, Code Interpreter, AG-UI SSE streaming.
78+
79+
### AG-UI LangGraph (`agui-langgraph-agent`)
80+
81+
**Location**: `agent_patterns/agui-langgraph-agent/`
82+
83+
Uses the `copilotkit` python library (`LangGraphAGUIAgent`) to wrap a LangGraph compiled graph. Uses `ActorAwareLangGraphAgent`, a subclass that rebuilds the graph per-request to ensure fresh Gateway MCP tool connections with valid tokens.
84+
85+
**Includes**: AgentCore Memory (checkpointer), Gateway MCP tools, Code Interpreter, CopilotKit middleware, AG-UI SSE streaming.
86+
87+
---
88+
89+
## Frontend
90+
91+
### Parser Auto-Selection
92+
93+
The AG-UI parser is automatically selected based on the pattern name prefix. Any pattern starting with `agui-` uses the AG-UI parser (`parsers/agui.ts`). Unlike the HTTP patterns — which each require a framework-specific parser (Strands, LangGraph, Claude) to handle their different streaming formats — all AG-UI patterns share a single parser. This is one of the key benefits of the AG-UI protocol: the backend framework is abstracted away behind a standard event vocabulary, so the frontend doesn't need to know whether the agent uses Strands or LangGraph.
94+
95+
See `frontend/src/lib/agentcore-client/client.ts` for the parser selection logic and `infra-cdk/config.yaml` comments for the full prefix-to-parser mapping.
96+
97+
### AG-UI Payload Format
98+
99+
The frontend automatically sends the correct payload format based on the pattern prefix. AG-UI patterns receive a `RunAgentInput` payload (with `threadId`, `messages`, `runId`), while HTTP patterns receive the standard `{ prompt, runtimeSessionId }` format. This is handled by `AgentCoreClient.invoke()`.
100+
101+
---
102+
103+
## Deployment
104+
105+
Set the pattern in `infra-cdk/config.yaml`:
106+
107+
```yaml
108+
backend:
109+
pattern: agui-strands-agent # or agui-langgraph-agent
110+
deployment_type: docker
111+
```
112+
113+
No CDK changes are required. The AG-UI patterns deploy as standard HTTP containers on AgentCore Runtime.
114+
115+
---
116+
117+
## CopilotKit Integration
118+
119+
[CopilotKit](https://www.copilotkit.ai/) is a React UI library that natively understands the AG-UI protocol. While FAST's built-in frontend includes a lightweight AG-UI parser for basic chat streaming, CopilotKit provides a richer set of capabilities for building agent-powered applications. Fullstack FAST applications with deeper CopilotKit integration can be found in the FAST samples repository (coming soon).
120+
121+
---
122+
123+
## Additional Resources
124+
125+
- [AG-UI Protocol Documentation](https://docs.ag-ui.com/concepts/overview)
126+
- [ag-ui-strands on PyPI](https://pypi.org/project/ag-ui-strands/)
127+
- [CopilotKit Documentation](https://docs.copilotkit.ai/)
128+
- [Strands AG-UI Integration Guide](https://strandsagents.com/docs/community/integrations/ag-ui/)

frontend/src/lib/agentcore-client/client.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,29 @@ import type { AgentCoreConfig, AgentPattern, ChunkParser, StreamCallback } from
55
import { parseStrandsChunk } from "./parsers/strands"
66
import { parseLanggraphChunk } from "./parsers/langgraph"
77
import { parseClaudeAgentSdkChunk } from "./parsers/claude-agent-sdk"
8+
import { parseAguiChunk } from "./parsers/agui"
89
import { readSSEStream } from "./utils/sse"
910

10-
const PARSERS: Record<AgentPattern, ChunkParser> = {
11-
"strands-single-agent": parseStrandsChunk,
12-
"langgraph-single-agent": parseLanggraphChunk,
13-
"claude-agent-sdk-single-agent": parseClaudeAgentSdkChunk,
14-
"claude-agent-sdk-multi-agent": parseClaudeAgentSdkChunk,
11+
/** Resolve parser from pattern prefix. Defaults to strands parser. */
12+
function getParser(pattern: AgentPattern): ChunkParser {
13+
if (pattern.startsWith("agui-")) return parseAguiChunk
14+
if (pattern.startsWith("langgraph-")) return parseLanggraphChunk
15+
if (pattern.startsWith("claude-")) return parseClaudeAgentSdkChunk
16+
if (pattern.startsWith("strands-")) return parseStrandsChunk
17+
return parseStrandsChunk
1518
}
1619

1720
export class AgentCoreClient {
1821
private runtimeArn: string
1922
private region: string
23+
private pattern: AgentPattern
2024
private parser: ChunkParser
2125

2226
constructor(config: AgentCoreConfig) {
2327
this.runtimeArn = config.runtimeArn
2428
this.region = config.region ?? "us-east-1"
25-
this.parser = PARSERS[config.pattern]
29+
this.pattern = config.pattern
30+
this.parser = getParser(config.pattern)
2631
}
2732

2833
generateSessionId(): string {
@@ -44,6 +49,22 @@ export class AgentCoreClient {
4449

4550
const traceId = `1-${Math.floor(Date.now() / 1000).toString(16)}-${crypto.randomUUID()}`
4651

52+
// Build payload based on pattern — AG-UI protocol expects a different format
53+
const body = this.pattern.startsWith("agui-")
54+
? {
55+
threadId: sessionId,
56+
runId: crypto.randomUUID(),
57+
messages: [{ id: crypto.randomUUID(), role: "user", content: query }],
58+
state: {},
59+
tools: [],
60+
context: [],
61+
forwardedProps: {},
62+
}
63+
: {
64+
prompt: query,
65+
runtimeSessionId: sessionId,
66+
}
67+
4768
// User identity is extracted server-side from the validated JWT token
4869
// (Authorization header), not sent in the payload body. This prevents
4970
// impersonation via prompt injection.
@@ -55,10 +76,7 @@ export class AgentCoreClient {
5576
"Content-Type": "application/json",
5677
"X-Amzn-Bedrock-AgentCore-Runtime-Session-Id": sessionId,
5778
},
58-
body: JSON.stringify({
59-
prompt: query,
60-
runtimeSessionId: sessionId,
61-
}),
79+
body: JSON.stringify(body),
6280
})
6381

6482
if (!response.ok) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import type { ChunkParser } from "../types"
5+
6+
/**
7+
* Parses SSE chunks from AG-UI (ag-ui-strands) agents.
8+
*
9+
* AG-UI events arrive as `data: <JSON>` where each JSON object has a `type` field:
10+
* RUN_STARTED, STATE_SNAPSHOT, TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT,
11+
* TEXT_MESSAGE_END, TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END,
12+
* TOOL_CALL_RESULT, RUN_FINISHED
13+
*/
14+
export const parseAguiChunk: ChunkParser = (line, callback) => {
15+
if (!line.startsWith("data: ")) return
16+
17+
const data = line.substring(6).trim()
18+
if (!data) return
19+
20+
try {
21+
const json = JSON.parse(data)
22+
const eventType: string = json.type
23+
24+
switch (eventType) {
25+
case "TEXT_MESSAGE_CONTENT":
26+
callback({ type: "text", content: json.delta ?? "" })
27+
break
28+
29+
case "TOOL_CALL_START":
30+
callback({
31+
type: "tool_use_start",
32+
toolUseId: json.toolCallId,
33+
name: json.toolCallName,
34+
})
35+
break
36+
37+
case "TOOL_CALL_ARGS":
38+
callback({
39+
type: "tool_use_delta",
40+
toolUseId: json.toolCallId,
41+
input: json.delta ?? "",
42+
})
43+
break
44+
45+
case "TOOL_CALL_RESULT":
46+
callback({
47+
type: "tool_result",
48+
toolUseId: json.toolCallId,
49+
result: json.content ?? "",
50+
})
51+
break
52+
53+
case "RUN_FINISHED":
54+
callback({ type: "result", stopReason: "end_turn" })
55+
callback({ type: "lifecycle", event: "run_finished" })
56+
break
57+
58+
case "RUN_STARTED":
59+
callback({ type: "lifecycle", event: "run_started" })
60+
break
61+
62+
case "TEXT_MESSAGE_START":
63+
callback({ type: "lifecycle", event: "message_start" })
64+
break
65+
66+
case "TEXT_MESSAGE_END":
67+
callback({ type: "lifecycle", event: "message_end" })
68+
break
69+
70+
case "STATE_SNAPSHOT":
71+
case "TOOL_CALL_END":
72+
// Informational — no action needed
73+
break
74+
75+
default:
76+
console.debug("Unhandled AG-UI event type:", eventType)
77+
}
78+
} catch {
79+
console.debug("Failed to parse AG-UI event:", data)
80+
}
81+
}

frontend/src/lib/agentcore-client/parsers/langgraph.ts

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@ import type { ChunkParser } from "../types"
66
/**
77
* Parses SSE chunks from LangGraph agents (stream_mode="messages").
88
*
9-
* Each SSE line is a model_dump() of either AIMessageChunk or ToolMessage.
9+
* Uses LangChain's normalized message fields — works with any ChatModel backend.
1010
*
11-
* AIMessageChunk shapes:
12-
* - text: content: [{type:"text", text:"...", index:N}]
13-
* - tool start: content: [{type:"tool_use", id, name, input:{}, index:N}] + tool_calls[]
14-
* - tool delta: content: [{type:"tool_use", partial_json:"...", index:N}] + tool_call_chunks[]
15-
* - stop: response_metadata: {stop_reason:"end_turn"|"tool_use"}
16-
* - last: chunk_position: "last"
17-
*
18-
* ToolMessage shape:
19-
* - type: "tool", content: "result", name: "tool_name", tool_call_id: "id"
11+
* Stream format:
12+
* AIMessageChunk:
13+
* - text: content (string)
14+
* - tool start: tool_call_chunks[{id, name, args}] — first chunk has id + name
15+
* - tool delta: tool_call_chunks[{args}] — subsequent chunks have args only
16+
* - stop: response_metadata.stop_reason
17+
* ToolMessage:
18+
* - type: "tool", content: string, tool_call_id: string
2019
*/
2120

22-
// Track current tool_use_id across chunks (deltas don't carry the id)
21+
// Track current tool_use_id — streaming deltas may omit the id
2322
let currentToolUseId = ""
2423

2524
export const parseLanggraphChunk: ChunkParser = (line, callback) => {
@@ -43,39 +42,29 @@ export const parseLanggraphChunk: ChunkParser = (line, callback) => {
4342

4443
// AIMessageChunk
4544
if (json.type === "AIMessageChunk") {
46-
// Content as plain string (when model responds without tool use)
45+
// Text token — content can be a string or array of text blocks
4746
if (typeof json.content === "string" && json.content) {
4847
callback({ type: "text", content: json.content })
49-
}
50-
51-
// Content as array of blocks (when model uses tools)
52-
if (Array.isArray(json.content)) {
48+
} else if (Array.isArray(json.content)) {
5349
for (const block of json.content) {
54-
// Text token
5550
if (block.type === "text" && block.text) {
5651
callback({ type: "text", content: block.text })
5752
}
53+
}
54+
}
5855

59-
// Tool use start — has id and name
60-
if (block.type === "tool_use" && block.id && block.name) {
61-
currentToolUseId = block.id
62-
callback({
63-
type: "tool_use_start",
64-
toolUseId: block.id,
65-
name: block.name,
66-
})
56+
// Tool calls — streamed via tool_call_chunks (LangChain's standard streaming field)
57+
if (Array.isArray(json.tool_call_chunks)) {
58+
for (const chunk of json.tool_call_chunks) {
59+
if (chunk.id && chunk.name) {
60+
currentToolUseId = chunk.id
61+
callback({ type: "tool_use_start", toolUseId: chunk.id, name: chunk.name })
6762
}
68-
69-
// Tool input streaming — has partial_json
70-
if (
71-
block.type === "tool_use" &&
72-
typeof block.partial_json === "string" &&
73-
block.partial_json
74-
) {
63+
if (typeof chunk.args === "string" && chunk.args) {
7564
callback({
7665
type: "tool_use_delta",
77-
toolUseId: currentToolUseId,
78-
input: block.partial_json,
66+
toolUseId: chunk.id || currentToolUseId,
67+
input: chunk.args,
7968
})
8069
}
8170
}

frontend/src/lib/agentcore-client/types.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
/** Supported agent framework patterns */
4+
/** Supported agent pattern prefixes — determines the frontend parser */
55
export type AgentPattern =
6-
| "strands-single-agent"
7-
| "langgraph-single-agent"
8-
| "claude-agent-sdk-single-agent"
9-
| "claude-agent-sdk-multi-agent"
6+
| `agui-${string}`
7+
| `strands-${string}`
8+
| `langgraph-${string}`
9+
| `claude-${string}`
1010

1111
/** Configuration for AgentCoreClient */
1212
export interface AgentCoreConfig {

infra-cdk/config.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ stack_name_base: FAST-stack
55
admin_user_email: # Example: admin@example.com
66

77
backend:
8-
pattern: strands-single-agent # Available patterns: strands-single-agent, langgraph-single-agent, claude-agent-sdk
8+
pattern: strands-single-agent # See patterns/ for available options
9+
# Pattern prefix determines the frontend parser:
10+
# agui-* → AG-UI parser (e.g. agui-strands-agent)
11+
# strands-* → Strands parser (e.g. strands-single-agent)
12+
# langgraph-* → LangGraph parser (e.g. langgraph-single-agent)
13+
# claude-* → Claude parser (e.g. claude-agent-sdk-single-agent)
14+
# The folder name must match exactly and start with the desired parser prefix.
915
deployment_type: docker # Available deployment types: docker (default), zip (not supported for claude-agent-sdk)
1016
network_mode: PUBLIC # Available network modes: PUBLIC (default), VPC
1117

0 commit comments

Comments
 (0)