docs: 添加 ag-ui 和 crewai 项目 skills 及更新文档
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
# AG-UI 示例脚本
|
||||
|
||||
本目录包含 AG-UI 协议的实现示例,帮助开发者快速上手。
|
||||
|
||||
## 前置要求
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install @ag-ui/client rxjs
|
||||
|
||||
# 或
|
||||
pnpm add @ag-ui/client rxjs
|
||||
```
|
||||
|
||||
## 示例列表
|
||||
|
||||
### 1. minimal_agent.ts - 最小 Agent 实现
|
||||
|
||||
展示如何创建一个最基本的 AG-UI Agent,实现事件流。
|
||||
|
||||
**核心概念**:
|
||||
- 继承 `AbstractAgent` 类
|
||||
- 实现 `run()` 方法返回 Observable 事件流
|
||||
- 发送生命周期事件 (RUN_STARTED/RUN_FINISHED)
|
||||
- 发送文本消息事件 (TEXT_MESSAGE_START/CONTENT/END)
|
||||
|
||||
**运行**:
|
||||
```bash
|
||||
# 使用 ts-node
|
||||
npx ts-node scripts/minimal_agent.ts
|
||||
|
||||
# 或编译后运行
|
||||
npx tsc scripts/minimal_agent.ts --esModuleInterop
|
||||
node scripts/minimal_agent.js
|
||||
```
|
||||
|
||||
**参考文档**: [modules/agents.md](../modules/agents.md) 行 132-197
|
||||
|
||||
---
|
||||
|
||||
### 2. tool_call_example.ts - 工具调用流程
|
||||
|
||||
展示 Agent 如何调用工具并流式传输参数和结果。
|
||||
|
||||
**核心概念**:
|
||||
- 定义工具 (Tool)
|
||||
- 工具调用事件流: ToolCallStart → ToolCallArgs → ToolCallEnd → ToolCallResult
|
||||
- 流式传输工具参数(分块发送)
|
||||
- 基于工具结果生成响应
|
||||
|
||||
**事件流**:
|
||||
```
|
||||
ToolCallStart (工具名称)
|
||||
↓
|
||||
ToolCallArgs (参数片段 1)
|
||||
ToolCallArgs (参数片段 2)
|
||||
ToolCallArgs (参数片段 3)
|
||||
↓
|
||||
ToolCallEnd (参数传输完成)
|
||||
↓
|
||||
ToolCallResult (工具执行结果)
|
||||
```
|
||||
|
||||
**运行**:
|
||||
```bash
|
||||
npx ts-node scripts/tool_call_example.ts
|
||||
```
|
||||
|
||||
**参考文档**: [modules/events.md](../modules/events.md) 行 938-1066
|
||||
|
||||
---
|
||||
|
||||
### 3. state_sync_example.ts - 状态同步
|
||||
|
||||
展示 Agent 与前端的 Snapshot-Delta 状态同步模式。
|
||||
|
||||
**核心概念**:
|
||||
- StateSnapshot - 完整状态快照
|
||||
- StateDelta - 增量更新 (JSON Patch RFC 6902)
|
||||
- MessagesSnapshot - 消息历史快照
|
||||
- 前端状态管理器实现
|
||||
|
||||
**状态同步模式**:
|
||||
```
|
||||
初始同步:
|
||||
StateSnapshot (完整状态)
|
||||
MessagesSnapshot (消息历史)
|
||||
↓
|
||||
增量更新:
|
||||
StateDelta (JSON Patch 操作)
|
||||
StateDelta (另一个更新)
|
||||
↓
|
||||
周期性刷新:
|
||||
StateSnapshot (确保一致性)
|
||||
```
|
||||
|
||||
**JSON Patch 操作类型**:
|
||||
- `replace` - 替换值
|
||||
- `add` - 添加字段
|
||||
- `remove` - 删除字段
|
||||
|
||||
**运行**:
|
||||
```bash
|
||||
npx ts-node scripts/state_sync_example.ts
|
||||
```
|
||||
|
||||
**参考文档**: [modules/events.md](../modules/events.md) 行 1067-1155
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 这些示例可以直接用于生产环境吗?
|
||||
|
||||
A: 这些示例仅用于教学目的。生产环境应考虑:
|
||||
- 错误处理和重试机制
|
||||
- 认证和授权
|
||||
- 日志和监控
|
||||
- 性能优化(如事件节流)
|
||||
|
||||
### Q: 如何处理工具调用的并发?
|
||||
|
||||
A: 每个工具调用有唯一的 `toolCallId`,可以并发执行多个工具:
|
||||
```typescript
|
||||
// 工具调用 1
|
||||
ToolCallStart(toolCallId: "tool_1")
|
||||
ToolCallArgs(toolCallId: "tool_1", delta: "...")
|
||||
ToolCallEnd(toolCallId: "tool_1")
|
||||
|
||||
// 工具调用 2(并发)
|
||||
ToolCallStart(toolCallId: "tool_2")
|
||||
ToolCallArgs(toolCallId: "tool_2", delta: "...")
|
||||
ToolCallEnd(toolCallId: "tool_2")
|
||||
```
|
||||
|
||||
### Q: StateDelta 的 JSON Patch 格式如何工作?
|
||||
|
||||
A: JSON Patch (RFC 6902) 是标准的增量更新格式:
|
||||
```json
|
||||
[
|
||||
{ "op": "replace", "path": "/session/currentPage", "value": 2 },
|
||||
{ "op": "add", "path": "/formData", "value": {...} },
|
||||
{ "op": "remove", "path": "/tempField" }
|
||||
]
|
||||
```
|
||||
|
||||
推荐使用 [fast-json-patch](https://github.com/Starcounter-Jack/Fast-JSON-Patch) 库处理。
|
||||
|
||||
---
|
||||
|
||||
## 进阶示例
|
||||
|
||||
需要更复杂的示例?查看官方仓库:
|
||||
- [AG-UI GitHub](https://github.com/ag-ui/ag-ui)
|
||||
- [CopilotKit Examples](https://github.com/CopilotKit/CopilotKit/tree/main/examples)
|
||||
|
||||
---
|
||||
|
||||
## 相关资源
|
||||
|
||||
- [AG-UI 协议文档](../llms-full.txt) - 完整协议规范
|
||||
- [模块索引](../SKILL.md#模块索引) - 按功能查找文档
|
||||
- [常见事件速查](../SKILL.md#常见事件速查) - 高频事件流程
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 最小 AG-UI Agent 实现示例
|
||||
*
|
||||
* 展示如何创建一个自定义 Agent,实现基本的事件流
|
||||
*
|
||||
* 参考文档: modules/agents.md (行 132-197)
|
||||
*/
|
||||
|
||||
import {
|
||||
AbstractAgent,
|
||||
RunAgent,
|
||||
RunAgentInput,
|
||||
EventType,
|
||||
BaseEvent,
|
||||
} from "@ag-ui/client"
|
||||
import { Observable } from "rxjs"
|
||||
|
||||
class MinimalAgent extends AbstractAgent {
|
||||
/**
|
||||
* 实现 run 方法,返回事件流
|
||||
*/
|
||||
run(input: RunAgentInput): RunAgent {
|
||||
const { threadId, runId } = input
|
||||
|
||||
return () =>
|
||||
new Observable<BaseEvent>((observer) => {
|
||||
// 1. 发送 RUN_STARTED 事件
|
||||
observer.next({
|
||||
type: EventType.RUN_STARTED,
|
||||
threadId,
|
||||
runId,
|
||||
})
|
||||
|
||||
// 2. 发送文本消息
|
||||
const messageId = Date.now().toString()
|
||||
|
||||
// 消息开始
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_START,
|
||||
messageId,
|
||||
role: "assistant",
|
||||
})
|
||||
|
||||
// 消息内容(流式)
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_CONTENT,
|
||||
messageId,
|
||||
delta: "Hello! ",
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_CONTENT,
|
||||
messageId,
|
||||
delta: "I'm a minimal AG-UI agent.",
|
||||
})
|
||||
|
||||
// 消息结束
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_END,
|
||||
messageId,
|
||||
})
|
||||
|
||||
// 3. 发送 RUN_FINISHED 事件
|
||||
observer.next({
|
||||
type: EventType.RUN_FINISHED,
|
||||
threadId,
|
||||
runId,
|
||||
})
|
||||
|
||||
// 完成流
|
||||
observer.complete()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const agent = new MinimalAgent({
|
||||
agentId: "minimal-agent",
|
||||
description: "A minimal AG-UI agent example",
|
||||
})
|
||||
|
||||
// 运行 Agent 并订阅事件流
|
||||
agent
|
||||
.runAgent({
|
||||
runId: "run_123",
|
||||
threadId: "thread_456",
|
||||
messages: [],
|
||||
tools: [],
|
||||
context: [],
|
||||
})
|
||||
.subscribe({
|
||||
next: (event) => {
|
||||
console.log(`[${event.type}]`, event)
|
||||
},
|
||||
error: (error) => console.error("Error:", error),
|
||||
complete: () => console.log("Agent run completed"),
|
||||
})
|
||||
|
||||
export { MinimalAgent }
|
||||
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* AG-UI 状态同步示例
|
||||
*
|
||||
* 展示 Agent 与前端的 Snapshot-Delta 状态同步模式
|
||||
*
|
||||
* 参考文档: modules/events.md (行 1067-1155)
|
||||
*
|
||||
* 状态管理模式:
|
||||
* 1. StateSnapshot - 完整状态快照(初始同步/周期性刷新)
|
||||
* 2. StateDelta - 增量更新(JSON Patch RFC 6902)
|
||||
* 3. MessagesSnapshot - 消息历史快照
|
||||
*/
|
||||
|
||||
import {
|
||||
AbstractAgent,
|
||||
RunAgent,
|
||||
RunAgentInput,
|
||||
EventType,
|
||||
BaseEvent,
|
||||
} from "@ag-ui/client"
|
||||
import { Observable } from "rxjs"
|
||||
|
||||
/**
|
||||
* Agent 状态定义示例
|
||||
*/
|
||||
interface AgentState {
|
||||
user: {
|
||||
name: string
|
||||
preferences: {
|
||||
theme: "light" | "dark"
|
||||
language: string
|
||||
}
|
||||
}
|
||||
session: {
|
||||
currentPage: number
|
||||
itemsPerPage: number
|
||||
totalItems: number
|
||||
}
|
||||
formData?: {
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
class StateSyncAgent extends AbstractAgent {
|
||||
private state: AgentState = {
|
||||
user: {
|
||||
name: "Alice",
|
||||
preferences: {
|
||||
theme: "light",
|
||||
language: "en",
|
||||
},
|
||||
},
|
||||
session: {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 10,
|
||||
totalItems: 100,
|
||||
},
|
||||
}
|
||||
|
||||
run(input: RunAgentInput): RunAgent {
|
||||
const { threadId, runId } = input
|
||||
|
||||
return () =>
|
||||
new Observable<BaseEvent>((observer) => {
|
||||
observer.next({
|
||||
type: EventType.RUN_STARTED,
|
||||
threadId,
|
||||
runId,
|
||||
})
|
||||
|
||||
// 1. 发送初始状态快照
|
||||
observer.next({
|
||||
type: EventType.STATE_SNAPSHOT,
|
||||
snapshot: this.state,
|
||||
})
|
||||
|
||||
// 2. 发送消息历史快照
|
||||
observer.next({
|
||||
type: EventType.MESSAGES_SNAPSHOT,
|
||||
messages: [
|
||||
{
|
||||
id: "msg_1",
|
||||
role: "user",
|
||||
content: "Show me page 2",
|
||||
},
|
||||
{
|
||||
id: "msg_2",
|
||||
role: "assistant",
|
||||
content: "Loading page 2...",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// 3. 模拟状态变化 - 分页更新
|
||||
setTimeout(() => {
|
||||
// 发送 Delta 更新(JSON Patch 格式)
|
||||
observer.next({
|
||||
type: EventType.STATE_DELTA,
|
||||
delta: [
|
||||
{ op: "replace", path: "/session/currentPage", value: 2 },
|
||||
],
|
||||
})
|
||||
}, 500)
|
||||
|
||||
// 4. 模拟用户偏好更新
|
||||
setTimeout(() => {
|
||||
observer.next({
|
||||
type: EventType.STATE_DELTA,
|
||||
delta: [
|
||||
{ op: "replace", path: "/user/preferences/theme", value: "dark" },
|
||||
],
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
// 5. 添加新字段(表单数据)
|
||||
setTimeout(() => {
|
||||
observer.next({
|
||||
type: EventType.STATE_DELTA,
|
||||
delta: [
|
||||
{
|
||||
op: "add",
|
||||
path: "/formData",
|
||||
value: {
|
||||
searchQuery: "AG-UI tutorial",
|
||||
filters: ["beginner", "typescript"],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}, 1500)
|
||||
|
||||
// 6. 周期性完整快照(确保状态一致性)
|
||||
setTimeout(() => {
|
||||
const updatedState: AgentState = {
|
||||
...this.state,
|
||||
session: {
|
||||
...this.state.session,
|
||||
currentPage: 2,
|
||||
},
|
||||
user: {
|
||||
...this.state.user,
|
||||
preferences: {
|
||||
...this.state.user.preferences,
|
||||
theme: "dark",
|
||||
},
|
||||
},
|
||||
formData: {
|
||||
searchQuery: "AG-UI tutorial",
|
||||
filters: ["beginner", "typescript"],
|
||||
},
|
||||
}
|
||||
|
||||
observer.next({
|
||||
type: EventType.STATE_SNAPSHOT,
|
||||
snapshot: updatedState,
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.RUN_FINISHED,
|
||||
threadId,
|
||||
runId,
|
||||
})
|
||||
|
||||
observer.complete()
|
||||
}, 2000)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端状态管理示例(接收端)
|
||||
*/
|
||||
class StateManager {
|
||||
private state: AgentState | null = null
|
||||
|
||||
handleEvent(event: BaseEvent) {
|
||||
switch (event.type) {
|
||||
case EventType.STATE_SNAPSHOT:
|
||||
// 完整替换状态
|
||||
this.state = (event as any).snapshot as AgentState
|
||||
console.log("[State] Snapshot received:", this.state)
|
||||
break
|
||||
|
||||
case EventType.STATE_DELTA:
|
||||
// 应用 JSON Patch 增量更新
|
||||
if (this.state) {
|
||||
const patches = (event as any).delta
|
||||
this.state = this.applyPatches(this.state, patches)
|
||||
console.log("[State] Delta applied:", patches)
|
||||
console.log("[State] Current state:", this.state)
|
||||
}
|
||||
break
|
||||
|
||||
case EventType.MESSAGES_SNAPSHOT:
|
||||
console.log("[Messages] Snapshot:", (event as any).messages)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用 JSON Patch 操作(简化实现)
|
||||
* 生产环境应使用 json-patch 库
|
||||
*/
|
||||
private applyPatches(state: AgentState, patches: any[]): AgentState {
|
||||
const newState = JSON.parse(JSON.stringify(state))
|
||||
|
||||
for (const patch of patches) {
|
||||
const { op, path, value } = patch
|
||||
const pathParts = path.split("/").filter(Boolean)
|
||||
let target: any = newState
|
||||
|
||||
// 导航到目标对象的父级
|
||||
for (let i = 0; i < pathParts.length - 1; i++) {
|
||||
target = target[pathParts[i]]
|
||||
}
|
||||
|
||||
const lastKey = pathParts[pathParts.length - 1]
|
||||
|
||||
switch (op) {
|
||||
case "replace":
|
||||
target[lastKey] = value
|
||||
break
|
||||
case "add":
|
||||
target[lastKey] = value
|
||||
break
|
||||
case "remove":
|
||||
delete target[lastKey]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const agent = new StateSyncAgent()
|
||||
const stateManager = new StateManager()
|
||||
|
||||
agent
|
||||
.runAgent({
|
||||
runId: "run_state_sync",
|
||||
threadId: "thread_123",
|
||||
messages: [],
|
||||
tools: [],
|
||||
context: [],
|
||||
})
|
||||
.subscribe({
|
||||
next: (event) => {
|
||||
stateManager.handleEvent(event)
|
||||
},
|
||||
complete: () => console.log("\n[Complete] State sync demo finished"),
|
||||
})
|
||||
|
||||
export { StateSyncAgent, StateManager, AgentState }
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* AG-UI 工具调用示例
|
||||
*
|
||||
* 展示 Agent 如何调用工具并流式传输参数和结果
|
||||
*
|
||||
* 参考文档: modules/events.md (行 938-1066)
|
||||
*
|
||||
* 事件流:
|
||||
* 1. ToolCallStart - 工具调用开始
|
||||
* 2. ToolCallArgs (多次) - 流式传输参数
|
||||
* 3. ToolCallEnd - 参数传输完成
|
||||
* 4. ToolCallResult - 工具执行结果
|
||||
*/
|
||||
|
||||
import {
|
||||
AbstractAgent,
|
||||
RunAgent,
|
||||
RunAgentInput,
|
||||
EventType,
|
||||
BaseEvent,
|
||||
} from "@ag-ui/client"
|
||||
import { Observable } from "rxjs"
|
||||
|
||||
/**
|
||||
* 工具定义示例
|
||||
*/
|
||||
interface Tool {
|
||||
name: string
|
||||
description: string
|
||||
parameters: Record<string, unknown>
|
||||
}
|
||||
|
||||
const weatherTool: Tool = {
|
||||
name: "get_weather",
|
||||
description: "Get current weather for a location",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
location: {
|
||||
type: "string",
|
||||
description: "City name",
|
||||
},
|
||||
unit: {
|
||||
type: "string",
|
||||
enum: ["celsius", "fahrenheit"],
|
||||
},
|
||||
},
|
||||
required: ["location"],
|
||||
},
|
||||
}
|
||||
|
||||
class ToolCallingAgent extends AbstractAgent {
|
||||
run(input: RunAgentInput): RunAgent {
|
||||
const { threadId, runId } = input
|
||||
|
||||
return () =>
|
||||
new Observable<BaseEvent>((observer) => {
|
||||
observer.next({
|
||||
type: EventType.RUN_STARTED,
|
||||
threadId,
|
||||
runId,
|
||||
})
|
||||
|
||||
// 模拟 Agent 分析用户请求后决定调用工具
|
||||
const toolCallId = `tool_${Date.now()}`
|
||||
const messageId = `msg_${Date.now()}`
|
||||
|
||||
// 1. 发送文本消息说明
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_START,
|
||||
messageId,
|
||||
role: "assistant",
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_CONTENT,
|
||||
messageId,
|
||||
delta: "Let me check the weather for you.",
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_END,
|
||||
messageId,
|
||||
})
|
||||
|
||||
// 2. 开始工具调用
|
||||
observer.next({
|
||||
type: EventType.TOOL_CALL_START,
|
||||
toolCallId,
|
||||
toolCallName: "get_weather",
|
||||
parentMessageId: messageId,
|
||||
})
|
||||
|
||||
// 3. 流式传输参数(分块发送)
|
||||
observer.next({
|
||||
type: EventType.TOOL_CALL_ARGS,
|
||||
toolCallId,
|
||||
delta: '{"loc', // 参数片段 1
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.TOOL_CALL_ARGS,
|
||||
toolCallId,
|
||||
delta: 'ation":', // 参数片段 2
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.TOOL_CALL_ARGS,
|
||||
toolCallId,
|
||||
delta: ' "San Francisco"}', // 参数片段 3
|
||||
})
|
||||
|
||||
// 4. 参数传输完成
|
||||
observer.next({
|
||||
type: EventType.TOOL_CALL_END,
|
||||
toolCallId,
|
||||
})
|
||||
|
||||
// 5. 工具执行结果(模拟)
|
||||
setTimeout(() => {
|
||||
observer.next({
|
||||
type: EventType.TOOL_CALL_RESULT,
|
||||
toolCallId,
|
||||
content: JSON.stringify({
|
||||
location: "San Francisco",
|
||||
temperature: "18°C",
|
||||
condition: "Partly cloudy",
|
||||
}),
|
||||
})
|
||||
|
||||
// 6. 基于工具结果的响应
|
||||
const responseMsgId = `msg_${Date.now()}_response`
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_START,
|
||||
messageId: responseMsgId,
|
||||
role: "assistant",
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_CONTENT,
|
||||
messageId: responseMsgId,
|
||||
delta: "The current weather in San Francisco is 18°C and partly cloudy.",
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.TEXT_MESSAGE_END,
|
||||
messageId: responseMsgId,
|
||||
})
|
||||
|
||||
observer.next({
|
||||
type: EventType.RUN_FINISHED,
|
||||
threadId,
|
||||
runId,
|
||||
})
|
||||
|
||||
observer.complete()
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const agent = new ToolCallingAgent()
|
||||
|
||||
agent
|
||||
.runAgent({
|
||||
runId: "run_tool_example",
|
||||
threadId: "thread_123",
|
||||
messages: [
|
||||
{
|
||||
id: "user_1",
|
||||
role: "user",
|
||||
content: "What's the weather in San Francisco?",
|
||||
},
|
||||
],
|
||||
tools: [weatherTool as any],
|
||||
context: [],
|
||||
})
|
||||
.subscribe({
|
||||
next: (event) => {
|
||||
switch (event.type) {
|
||||
case EventType.TOOL_CALL_START:
|
||||
console.log(`[Tool Call] Starting: ${(event as any).toolCallName}`)
|
||||
break
|
||||
case EventType.TOOL_CALL_ARGS:
|
||||
process.stdout.write((event as any).delta)
|
||||
break
|
||||
case EventType.TOOL_CALL_END:
|
||||
console.log("\n[Tool Call] Arguments complete")
|
||||
break
|
||||
case EventType.TOOL_CALL_RESULT:
|
||||
console.log("[Tool Result]", (event as any).content)
|
||||
break
|
||||
default:
|
||||
console.log(`[${event.type}]`)
|
||||
}
|
||||
},
|
||||
complete: () => console.log("Tool call flow completed"),
|
||||
})
|
||||
|
||||
export { ToolCallingAgent, weatherTool }
|
||||
Reference in New Issue
Block a user