Skip to content

Commit 087c82f

Browse files
authored
{server/agui, examples, docs}: support agui (#379)
1 parent 8754557 commit 087c82f

File tree

42 files changed

+3445
-0
lines changed

Some content is hidden

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

42 files changed

+3445
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
.vscode/
77
.idea/
8+
node_modules/
9+
.next/
810
*.out
911
*.log
1012
coverage.out
@@ -24,3 +26,4 @@ CLAUDE.md
2426
*test*.py
2527
*.db
2628
.codex
29+
pnpm-lock.yaml
Lines changed: 3 additions & 0 deletions
Loading

docs/mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ nav:
2424
- Planner: planner.md
2525
- Event: event.md
2626
- Debug: debugserver.md
27+
- AG-UI: agui.md
2728
- Observability: observability.md
2829
- A2A: a2a.md
2930
- Ecosystem: ecosystem.md
@@ -61,6 +62,7 @@ plugins:
6162
- Graph: graph.md
6263
- Event: event.md
6364
- 调试: debugserver.md
65+
- AG-UI: agui.md
6466
- 可观测: observability.md
6567
- A2A: a2a.md
6668
- 生态: ecosystem.md
Lines changed: 3 additions & 0 deletions
Loading

docs/mkdocs/en/agui.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# AG-UI Guide
2+
3+
The AG-UI (Agent-User Interaction) protocol is maintained by the open-source [AG-UI Protocol](https://github.com/ag-ui-protocol/ag-ui) project. It enables agents built in different languages, frameworks, and execution environments to deliver their runtime outputs to user interfaces through a unified event stream. The protocol tolerates loosely matched payloads and supports transports such as SSE and WebSocket.
4+
5+
`tRPC-Agent-Go` ships with native AG-UI integration. It provides an SSE server implementation by default, while also allowing you to swap in a custom `service.Service` to use transports like WebSocket and to extend the event translation logic.
6+
7+
## Getting Started
8+
9+
Assuming you already have an agent, you can expose it via the AG-UI protocol with just a few lines of code:
10+
11+
```go
12+
import (
13+
"net/http"
14+
15+
"trpc.group/trpc-go/trpc-agent-go/runner"
16+
"trpc.group/trpc-go/trpc-agent-go/server/agui"
17+
)
18+
19+
// Create the agent.
20+
agent := newAgent()
21+
// Build the Runner that will execute the agent.
22+
runner := runner.NewRunner(agent.Info().Name, agent)
23+
// Create the AG-UI server and mount it on an HTTP route.
24+
server, err := agui.New(runner, agui.WithPath("/agui"))
25+
if err != nil {
26+
log.Fatalf("create agui server failed: %v", err)
27+
}
28+
// Start the HTTP listener.
29+
if err := http.ListenAndServe("127.0.0.1:8080", server.Handler()); err != nil {
30+
log.Fatalf("server stopped with error: %v", err)
31+
}
32+
```
33+
34+
A complete version of this example lives in [examples/agui/server/default](https://github.com/trpc-group/trpc-agent-go/tree/main/examples/agui/server/default).
35+
36+
For an in-depth guide to Runners, refer to the [runner](./runner.md) documentation.
37+
38+
On the client side you can pair the server with frameworks that understand the AG-UI protocol, such as [CopilotKit](https://github.com/CopilotKit/CopilotKit). It provides React/Next.js components with built-in SSE subscriptions. The sample at [examples/agui/client/copilotkit](https://github.com/trpc-group/trpc-agent-go/tree/main/examples/agui/client/copilotkit) builds a web UI that communicates with the agent through AG-UI, as shown below.
39+
40+
![copilotkit](../assets/img/agui/copilotkit.png)
41+
42+
## Advanced Usage
43+
44+
### Custom transport
45+
46+
The AG-UI specification does not enforce a transport. The framework uses SSE by default, but you can implement the `service.Service` interface to switch to WebSocket or any other transport:
47+
48+
```go
49+
import (
50+
"trpc.group/trpc-go/trpc-agent-go/runner"
51+
"trpc.group/trpc-go/trpc-agent-go/server/agui"
52+
)
53+
54+
type wsService struct{}
55+
56+
func (s *wsService) Handler() http.Handler { /* Register WebSocket and stream events. */ }
57+
58+
runner := runner.NewRunner(agent.Info().Name, agent)
59+
server, _ := agui.New(runner, agui.WithService(&wsService{}))
60+
```
61+
62+
### Custom translator
63+
64+
`translator.New` converts internal events into the standard AG-UI events. To enrich the stream while keeping the default behaviour, implement `translator.Translator` and use the AG-UI `Custom` event type to carry extra data:
65+
66+
```go
67+
import (
68+
aguievents "github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/core/events"
69+
agentevent "trpc.group/trpc-go/trpc-agent-go/event"
70+
"trpc.group/trpc-go/trpc-agent-go/runner"
71+
"trpc.group/trpc-go/trpc-agent-go/server/agui"
72+
"trpc.group/trpc-go/trpc-agent-go/server/agui/adapter"
73+
aguirunner "trpc.group/trpc-go/trpc-agent-go/server/agui/runner"
74+
"trpc.group/trpc-go/trpc-agent-go/server/agui/translator"
75+
)
76+
77+
type customTranslator struct {
78+
inner translator.Translator
79+
}
80+
81+
func (t *customTranslator) Translate(evt *agentevent.Event) ([]aguievents.Event, error) {
82+
out, err := t.inner.Translate(evt)
83+
if err != nil {
84+
return nil, err
85+
}
86+
if payload := buildCustomPayload(evt); payload != nil {
87+
out = append(out, aguievents.NewCustomEvent("trace.metadata", aguievents.WithValue(payload)))
88+
}
89+
return out, nil
90+
}
91+
92+
func buildCustomPayload(evt *agentevent.Event) map[string]any {
93+
if evt == nil || evt.Response == nil {
94+
return nil
95+
}
96+
return map[string]any{
97+
"object": evt.Response.Object,
98+
"timestamp": evt.Response.Timestamp,
99+
}
100+
}
101+
102+
factory := func(input *adapter.RunAgentInput) translator.Translator {
103+
return &customTranslator{inner: translator.New(input.ThreadID, input.RunID)}
104+
}
105+
106+
runner := runner.NewRunner(agent.Info().Name, agent)
107+
server, _ := agui.New(runner, agui.WithAGUIRunnerOptions(aguirunner.WithTranslatorFactory(factory)))
108+
```
109+
110+
### Custom `UserIDResolver`
111+
112+
By default every request maps to the fixed user ID `"user"`. Implement a custom `UserIDResolver` if you need to derive the user from the `RunAgentInput`:
113+
114+
```go
115+
import (
116+
"trpc.group/trpc-go/trpc-agent-go/runner"
117+
"trpc.group/trpc-go/trpc-agent-go/server/agui"
118+
"trpc.group/trpc-go/trpc-agent-go/server/agui/adapter"
119+
aguirunner "trpc.group/trpc-go/trpc-agent-go/server/agui/runner"
120+
)
121+
122+
resolver := func(ctx context.Context, input *adapter.RunAgentInput) (string, error) {
123+
if user, ok := input.ForwardedProps["userId"].(string); ok && user != "" {
124+
return user, nil
125+
}
126+
return "anonymous", nil
127+
}
128+
129+
runner := runner.NewRunner(agent.Info().Name, agent)
130+
server, _ := agui.New(runner, agui.WithAGUIRunnerOptions(aguirunner.WithUserIDResolver(resolver)))
131+
```

docs/mkdocs/zh/agui.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# AG-UI 使用指南
2+
3+
AG-UI(Agent-User Interaction)协议由开源社区 [AG-UI Protocol](https://github.com/ag-ui-protocol/ag-ui) 维护,旨在让不同语言、不同框架、不同执行环境的 Agent,都能够通过统一的事件流把执行过程中产生的内容传递给用户界面,允许松散的事件格式匹配,支持 SSE 和 WebSocket 等多种通信协议。
4+
5+
`tRPC-Agent-Go` 接入了 AG-UI 协议,默认提供 SSE 服务端实现,也支持通过自定义 `service.Service` 切换到 WebSocket 等通信协议,并扩展事件翻译逻辑。
6+
7+
## 快速上手
8+
9+
假设你已实现一个 Agent,可以按如下方式接入 AG-UI 协议并启动服务:
10+
11+
```go
12+
import (
13+
"net/http"
14+
15+
"trpc.group/trpc-go/trpc-agent-go/runner"
16+
"trpc.group/trpc-go/trpc-agent-go/server/agui"
17+
)
18+
19+
// 创建 Agent
20+
agent := newAgent()
21+
// 创建 Runner
22+
runner := runner.NewRunner(agent.Info().Name, agent)
23+
// 创建 AG-UI 服务,指定 HTTP 路由
24+
server, err := agui.New(runner, agui.WithPath("/agui"))
25+
if err != nil {
26+
log.Fatalf("create agui server failed: %v", err)
27+
}
28+
// 启动 HTTP 服务
29+
if err := http.ListenAndServe("127.0.0.1:8080", server.Handler()); err != nil {
30+
log.Fatalf("server stopped with error: %v", err)
31+
}
32+
```
33+
34+
完整代码示例参见 [examples/agui/server/default](https://github.com/trpc-group/trpc-agent-go/tree/main/examples/agui/server/default)
35+
36+
Runner 全面的使用方法参见 [runner](./runner.md)
37+
38+
在前端侧,可以配合 [CopilotKit](https://github.com/CopilotKit/CopilotKit) 等支持 AG-UI 协议的客户端框架,它提供 React/Next.js 组件并内置 SSE 订阅能力。[examples/agui/client/copilotkit](https://github.com/trpc-group/trpc-agent-go/tree/main/examples/agui/client/copilotkit) 使用 CopilotKit 搭建了 Web UI 界面,通过 AG-UI 协议与 Agent 通信,效果如下图所示。
39+
40+
![copilotkit](../assets/img/agui/copilotkit.png)
41+
42+
## 进阶用法
43+
44+
### 自定义通信协议
45+
46+
AG-UI 协议未强制规定通信协议,框架使用 SSE 作为 AG-UI 的默认通信协议,如果希望改用 WebSocket 等其他协议,可以实现 `service.Service` 接口:
47+
48+
```go
49+
import (
50+
"trpc.group/trpc-go/trpc-agent-go/runner"
51+
"trpc.group/trpc-go/trpc-agent-go/server/agui"
52+
)
53+
54+
type wsService struct{}
55+
56+
func (s *wsService) Handler() http.Handler { /* 注册 WebSocket 并写入事件 */ }
57+
58+
runner := runner.NewRunner(agent.Info().Name, agent)
59+
server, _ := agui.New(runner, agui.WithService(&wsService{}))
60+
```
61+
62+
### 自定义 Translator
63+
64+
默认的 `translator.New` 会把内部事件翻译成协议里定义的标准事件集。若想在保留默认行为的基础上追加自定义信息,可以实现 `translator.Translator` 接口,并借助 AG-UI 的 `Custom` 事件类型携带扩展数据:
65+
66+
```go
67+
import (
68+
aguievents "github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/core/events"
69+
"trpc.group/trpc-go/trpc-agent-go/runner"
70+
"trpc.group/trpc-go/trpc-agent-go/server/agui"
71+
"trpc.group/trpc-go/trpc-agent-go/server/agui/adapter"
72+
aguirunner "trpc.group/trpc-go/trpc-agent-go/server/agui/runner"
73+
"trpc.group/trpc-go/trpc-agent-go/server/agui/translator"
74+
)
75+
76+
type customTranslator struct {
77+
inner translator.Translator
78+
}
79+
80+
func (t *customTranslator) Translate(event *event.Event) ([]aguievents.Event, error) {
81+
out, err := t.inner.Translate(event)
82+
if err != nil {
83+
return nil, err
84+
}
85+
if payload := buildCustomPayload(event); payload != nil {
86+
out = append(out, aguievents.NewCustomEvent("trace.metadata", aguievents.WithValue(payload)))
87+
}
88+
return out, nil
89+
}
90+
91+
func buildCustomPayload(event *event.Event) map[string]any {
92+
if event == nil || event.Response == nil {
93+
return nil
94+
}
95+
return map[string]any{
96+
"object": event.Response.Object,
97+
"timestamp": event.Response.Timestamp,
98+
}
99+
}
100+
101+
factory := func(input *adapter.RunAgentInput) translator.Translator {
102+
return &customTranslator{inner: translator.New(input.ThreadID, input.RunID)}
103+
}
104+
105+
runner := runner.NewRunner(agent.Info().Name, agent)
106+
server, _ := agui.New(runner, agui.WithAGUIRunnerOptions(aguirunner.WithTranslatorFactory(factory)))
107+
```
108+
109+
### 自定义 `UserIDResolver`
110+
111+
默认所有请求都会归到固定的 `"user"` 用户 ID,可以通过自定义 `UserIDResolver``RunAgentInput` 中提取 `UserID`
112+
113+
```go
114+
import (
115+
"trpc.group/trpc-go/trpc-agent-go/runner"
116+
"trpc.group/trpc-go/trpc-agent-go/server/agui"
117+
"trpc.group/trpc-go/trpc-agent-go/server/agui/adapter"
118+
aguirunner "trpc.group/trpc-go/trpc-agent-go/server/agui/runner"
119+
)
120+
121+
resolver := func(ctx context.Context, input *adapter.RunAgentInput) (string, error) {
122+
if user, ok := input.ForwardedProps["userId"].(string); ok && user != "" {
123+
return user, nil
124+
}
125+
return "anonymous", nil
126+
}
127+
128+
runner := runner.NewRunner(agent.Info().Name, agent)
129+
server, _ := agui.New(runner, agui.WithAGUIRunnerOptions(aguirunner.WithUserIDResolver(resolver)))
130+
```

examples/agui/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# AG-UI Examples
2+
3+
This folder collects runnable demos that showcase how to integrate the `tRPC-Agent-Go` AG-UI server and various clients.
4+
5+
- [`client/`](client/) – Client-side samples.
6+
- [`server/`](server/) – Server-side samples.
7+
8+
## Quick Start
9+
10+
1. Start the default AG-UI server:
11+
12+
```bash
13+
go run ./server/default
14+
```
15+
16+
2. In another terminal start the CopilotKit client:
17+
18+
```bash
19+
cd ./client/copilotkit
20+
pnpm install
21+
pnpm dev
22+
```
23+
24+
3. Ask a question such as `Calculate 2*(10+11)` and watch the live event stream in the terminal. A full transcript example is documented in [`client/copilotkit/README.md`](client/copilotkit/README.md).
25+
26+
See the individual README files under `client/` and `server/` for more background and configuration options.

examples/agui/client/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# AG-UI Clients
2+
3+
Runnable client front-ends that consume the AG-UI SSE stream exposed by the example servers.
4+
5+
## Available Clients
6+
7+
- [copilotkit/](copilotkit/) – Next.js web chat built with CopilotKit that renders AG-UI responses in the browser.
8+
- [raw/](raw/) – Minimal Go terminal client that prints every SSE event for inspection.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# CopilotKit Front-End for the AG-UI Server
2+
3+
This example shows how to pair the Go-based AG-UI server with a React front-end built on [CopilotKit](https://docs.copilotkit.ai/). The UI streams Server-Sent Events from the AG-UI endpoint using the `@ag-ui/client` HTTP agent and renders an assistant sidebar provided by CopilotKit.
4+
5+
## Start the CopilotKit client
6+
7+
```bash
8+
pnpm install # or npm install
9+
pnpm dev # or npm run dev
10+
```
11+
12+
Available environment variables before `pnpm dev`:
13+
14+
- `AG_UI_ENDPOINT`: override the AG-UI endpoint URL (defaults to `http://127.0.0.1:8080/agui`).
15+
16+
Open `http://localhost:3000` and start chatting with the full-screen assistant UI. The input shows the placeholder `Calculate 2*(10+11)`, first explain the idea, then calculate, and finally give the conclusion.`—press Enter to run that scenario or type your own request. Tool calls and their results appear inline inside the chat transcript.
17+
18+
![agui-copilotkit](../../../../.resource/images/examples/agui-copilotkit.png)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// Tencent is pleased to support the open source community by making trpc-agent-go available.
3+
//
4+
// Copyright (C) 2025 Tencent. All rights reserved.
5+
//
6+
// trpc-agent-go is licensed under the Apache License Version 2.0.
7+
//
8+
//
9+
10+
import { NextRequest } from "next/server";
11+
import {
12+
CopilotRuntime,
13+
ExperimentalEmptyAdapter,
14+
copilotRuntimeNextJSAppRouterEndpoint,
15+
} from "@copilotkit/runtime";
16+
import { HttpAgent } from "@ag-ui/client";
17+
18+
const runtime = new CopilotRuntime({
19+
agents: {
20+
"agui-demo": new HttpAgent({
21+
agentId: "agui-demo",
22+
description: "AG-UI agent hosted by the Go evaluation server",
23+
threadId: "demo-thread",
24+
url: process.env.AG_UI_ENDPOINT ?? "http://127.0.0.1:8080/agui",
25+
headers: process.env.AG_UI_TOKEN
26+
? { Authorization: `Bearer ${process.env.AG_UI_TOKEN}` }
27+
: undefined,
28+
}),
29+
},
30+
});
31+
32+
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
33+
runtime,
34+
serviceAdapter: new ExperimentalEmptyAdapter(),
35+
endpoint: "/api/copilotkit",
36+
});
37+
38+
export async function POST(request: NextRequest) {
39+
return handleRequest(request);
40+
}

0 commit comments

Comments
 (0)