Skip to content

Commit 155d03c

Browse files
committed
update blades session
1 parent 9f13789 commit 155d03c

File tree

2 files changed

+252
-52
lines changed

2 files changed

+252
-52
lines changed
Lines changed: 127 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,163 @@
11
---
22
title: "Session and State"
3-
description: "Blades provides storage for contextual conversation history and multimodal content within a single dialogue."
3+
description: "blades provides storage for contextual conversation history and multimodal content within a single conversation"
44
reference: ["https://github.com/go-kratos/blades/tree/main/examples/state","https://github.com/go-kratos/blades/tree/main/examples/session"]
55
---
6-
Agents often need to access conversation history within a single dialogue to ensure awareness of what has been said and done, maintaining coherence and avoiding repetition. Blades provides foundational functionality for Agents through Session and State.
6+
77
## Core Concepts
8-
`Session` and `State` are core concepts in Blades used to provide conversational context information. However, they differ and are suitable for different scenarios.
98

10-
- **Session**: Represents the current conversation thread, indicating a one-on-one, continuous interaction between the user and the Agent.
9+
In multi-turn conversations or multi-Agent collaboration workflows, the system needs a place to carry context and accumulate intermediate outputs, so that subsequent steps can "continue from the previous step" rather than starting from scratch each time.
10+
11+
- Session: A container for a conversation thread. It is responsible for maintaining the shared context and state for a single interaction chain within one Run/RunStream.
12+
- State: Shared data storage within a session, used to save "reusable intermediate results" (e.g., draft, suggestions, tool outputs, parsed text, etc.).
13+
14+
In a nutshell:
15+
- Session = Runtime context container
16+
- State = Key-value data inside the container (map[string]any)
17+
18+
## State: Persisting Intermediate Results as Key-Value Pairs
19+
20+
### Data Structure
21+
In Blades, State can essentially be understood as: `map[string]any`
22+
23+
It is used to share data across steps (across Agents): the previous step writes, and the next Agent's Prompt template reads directly.
24+
25+
> The standalone "Kratos" line in your original text appears to be a mis-paste; it is recommended to delete it to avoid confusing readers.
26+
27+
### Writing to State: Using WithOutputKey to Store Output Under a Specific Key
28+
In an Agent's configuration, you can use the `WithOutputKey` method to specify which key in the State a particular step's output should be written to.
29+
30+
For example, a WriterAgent responsible for producing a draft writes its output to the key `draft`:
31+
```go
32+
writerAgent, err := blades.NewAgent(
33+
"WriterAgent",
34+
blades.WithModel(model),
35+
blades.WithInstruction("Draft a short paragraph on climate change."),
36+
blades.WithOutputKey("draft"),
37+
)
38+
```
39+
Similarly, a ReviewerAgent writes its output to the key `suggestions`:
40+
```go
41+
reviewerAgent, err := blades.NewAgent(
42+
"ReviewerAgent",
43+
blades.WithModel(model),
44+
blades.WithInstruction("Review the draft and suggest improvements."),
45+
blades.WithOutputKey("suggestions"),
46+
)
47+
```
48+
49+
### Reading State in Prompts: Directly Referencing Template Variables
50+
When you write Go templates ({{.draft}} / {{.suggestions}}) in WithInstruction, Blades injects the current Session's State into the template context, allowing you to use them directly like this:
51+
```go
52+
**Draft**
53+
{{.draft}}
54+
55+
Here are the suggestions to consider:
56+
{{.suggestions}}
57+
```
1158

12-
- **State**: Stores data within the current conversation (e.g., PDF documents in the dialogue).
59+
## Session: Creating, Initializing, and Reusing Within a Single Execution Chain
1360

14-
## State
15-
**`State`** is essentially a key-value data pair storage **`map[string]any`**. In Blades, you can store it using the **PutState** method of the session.
61+
### Creating a Session (Optionally Initializing State)
62+
You can create an empty session:
1663
```go
1764
session := blades.NewSession()
18-
session.PutState(agent.Name(), output.Text())
1965
```
20-
## Session
21-
Creating a `Session` in Blades is straightforward—simply call the **NewSession** method, which can accept State data to be stored in the conversation.
66+
Or start with an initial state (commonly used when there is already a draft, user information, or when resuming an interrupted workflow):
2267
```go
23-
session := blades.NewSession(states)
68+
session := blades.NewSession(map[string]any{
69+
"draft": "Climate change refers to long-term shifts in temperatures and weather patterns...",
70+
})
2471
```
25-
Here, `states` is of type **`map[string]any`**. Multiple **`State`** contents can be imported into a **`Session`**.
26-
### Session Example
27-
When using **`Session`** in Blades, you only need to pass the **`Session`** parameter in the **`NewRunner`** method.
72+
73+
### Injecting Session into Runner: Sharing State Across the Same Chain
74+
Only by injecting the session into the run (blades.WithSession(session)) will the State you mentioned earlier be shared throughout that execution chain.
75+
76+
- If you use runner.Run(...): pass blades.WithSession(session) as an option.
77+
- If you use runner.RunStream(...): similarly, you can pass the session option.
78+
79+
## Complete Example: Writer/Reviewer Sharing draft & suggestions in a Loop
80+
81+
Your code essentially implements a "writing-review" closed loop:
82+
1. WriterAgent generates a draft → writes to `draft`
83+
2. ReviewerAgent provides suggestions → writes to `suggestions`
84+
3. Loop condition check: if the reviewer thinks "draft is good", stop; otherwise, continue iterating
85+
4. In the next iteration, WriterAgent reads `draft` and `suggestions` in its instruction for targeted revision
86+
2887
```go
2988
package main
3089

3190
import (
3291
"context"
3392
"log"
93+
"os"
94+
"strings"
3495

3596
"github.com/go-kratos/blades"
3697
"github.com/go-kratos/blades/contrib/openai"
98+
"github.com/go-kratos/blades/flow"
3799
)
38100

39101
func main() {
40-
// Configure OpenAI API key and base URL using environment variables:
41-
model := openai.NewModel("gpt-5", openai.Config{
102+
model := openai.NewModel(os.Getenv("OPENAI_MODEL"), openai.Config{
42103
APIKey: os.Getenv("OPENAI_API_KEY"),
43104
})
44-
agent, err := blades.NewAgent(
45-
"History Tutor",
105+
writerAgent, err := blades.NewAgent(
106+
"WriterAgent",
46107
blades.WithModel(model),
47-
blades.WithInstruction("You are a knowledgeable history tutor. Provide detailed and accurate information on historical events."),
108+
blades.WithInstruction(`Draft a short paragraph on climate change.
109+
{{if .suggestions}}
110+
**Draft**
111+
{{.draft}}
112+
113+
Here are the suggestions to consider:
114+
{{.suggestions}}
115+
{{end}}
116+
`),
117+
blades.WithOutputKey("draft"),
48118
)
49119
if err != nil {
50120
log.Fatal(err)
51121
}
52-
input := blades.UserMessage("Can you tell me about the causes of World War II?")
53-
// Create a new session
54-
session := blades.NewSession()
55-
// Run the agent
56-
runner := blades.NewRunner(agent)
57-
output, err := runner.Run(context.Background(), input, blades.WithSession(session))
122+
reviewerAgent, err := blades.NewAgent(
123+
"ReviewerAgent",
124+
blades.WithModel(model),
125+
blades.WithInstruction(`Review the draft and suggest improvements.
126+
If the draft is good, respond with "The draft is good".
127+
128+
**Draft**
129+
{{.draft}}
130+
`),
131+
blades.WithOutputKey("suggestions"),
132+
)
58133
if err != nil {
59134
log.Fatal(err)
60135
}
61-
log.Println(output.Text())
136+
loopAgent := flow.NewLoopAgent(flow.LoopConfig{
137+
Name: "WritingReviewFlow",
138+
Description: "An agent that loops between writing and reviewing until the draft is good.",
139+
MaxIterations: 3,
140+
Condition: func(ctx context.Context, output *blades.Message) (bool, error) {
141+
return !strings.Contains(output.Text(), "The draft is good"), nil
142+
},
143+
SubAgents: []blades.Agent{
144+
writerAgent,
145+
reviewerAgent,
146+
},
147+
})
148+
input := blades.UserMessage("Please write a short paragraph about climate change.")
149+
runner := blades.NewRunner(loopAgent)
150+
stream := runner.RunStream(context.Background(), input)
151+
for message, err := range stream {
152+
if err != nil {
153+
log.Fatal(err)
154+
}
155+
log.Println(message.Author, message.Text())
156+
}
62157
}
63-
```
158+
```
159+
160+
## Best Practices
161+
- Use stable, readable key names: e.g., `draft`, `suggestions`. For complex projects, consider hierarchical naming: `writing.draft`, `review.suggestions`.
162+
- Avoid stuffing entire conversation history into State: State is better suited for "structured/reusable intermediate outputs." For historical conversation, consider using model messages or summaries.
163+
- Include session even for streaming output: If you want state to be shared across multiple steps, ensure the entire Run/RunStream executes under the same session.

src/content/docs/zh-cn/blades/tutorials/01-session.md

Lines changed: 125 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,61 +3,161 @@ title: "会话与状态"
33
description: "blades为在单次对话中存储上下文对话记录和多模态内容"
44
reference: ["https://github.com/go-kratos/blades/tree/main/examples/state","https://github.com/go-kratos/blades/tree/main/examples/session"]
55
---
6-
Agent常常需要在单次对话中获取对话历史,来确保已经说过和做过什么,避免保持连贯性和避免重复。Blades通过Session、State 来为Agent提供基础功能。
6+
77
## 核心概念
8-
`Session``State` 是Blades中用于提供对话上下文信息的核心概念。但两者有所不同,适合于不同的场景。
98

10-
- **Session**:表示当前对话线程,表示用户和Agent时1v1单次、持续的交互。
9+
在多轮对话或多 Agent 协作流程里,系统需要一个地方来承载上下文并沉淀中间产物,这样后续步骤才能“接着上一步继续做”,而不是每次从零开始。
10+
11+
- Session(会话):一次对话线程的容器。它负责在一次 Run/RunStream 的链路里,维护这一轮交互共享的上下文与状态。
12+
- State(状态):会话内的共享数据存储,用于保存“可复用的中间结果”(例如草稿 draft、审阅建议 suggestions、工具输出、解析后的文本等)。
13+
14+
一句话理解:
15+
- Session = 运行时上下文容器
16+
- State = 容器里的键值数据(map[string]any)
17+
18+
## State:用键值方式沉淀中间结果
19+
20+
### 数据结构
21+
在 Blades 中,State 本质上可以理解为:`map[string]any`
22+
23+
它用于跨步骤(跨 Agent)共享数据:上一步写入,下一个 Agent 的 Prompt 模板直接读取。
24+
25+
> 你原文里单独出现的 “Kratos” 一行看起来像误粘贴,建议删掉,避免读者困惑。
26+
27+
### 写入 State:用 WithOutputKey 把输出落到指定 key
28+
在 Agent 的配置里,可以通过 `WithOutputKey` 方法,指定某个步骤的输出结果要写入 State 里的哪个 key。
1129

12-
- **State**:存储当前对话中的数据(例如:对话中的pdf文档等)。
30+
比如 WriterAgent 负责产出草稿,把输出落到 draft:
31+
```go
32+
writerAgent, err := blades.NewAgent(
33+
"WriterAgent",
34+
blades.WithModel(model),
35+
blades.WithInstruction("Draft a short paragraph on climate change."),
36+
blades.WithOutputKey("draft"),
37+
)
38+
```
39+
同理 ReviewerAgent 的输出落到 suggestions:
40+
```go
41+
reviewerAgent, err := blades.NewAgent(
42+
"ReviewerAgent",
43+
blades.WithModel(model),
44+
blades.WithInstruction("Review the draft and suggest improvements."),
45+
blades.WithOutputKey("suggestions"),
46+
)
47+
```
1348

14-
## State
15-
**`State`** 实质为存储键值数据对 **`map[string]any`** ,在Blades中可以使用session的 **PutState** 方法存储。
49+
### 在 Prompt 里读取 State:模板变量直接引用
50+
当你在 WithInstruction 里写 Go template({{.draft}} / {{.suggestions}}),Blades 会把当前 Session 的 State 注入模板上下文,于是你能像下面这样直接使用:
51+
```go
52+
**Draft**
53+
{{.draft}}
54+
55+
Here are the suggestions to consider:
56+
{{.suggestions}}
57+
```
58+
59+
## Session:创建、初始化、并在一次运行链路中复用
60+
61+
### 创建 Session(可选初始化 State)
62+
你既可以创建空会话:
1663
```go
1764
session := blades.NewSession()
18-
session.PutState(agent.Name(), output.Text())
1965
```
20-
## Session
21-
在blades中创建 `Session` 十分简单,只需要执行 **NewSession** 方法,在方法中可传入对话中存储的数据State。
66+
也可以带着初始状态启动(常用于:已有草稿、已有用户信息、或恢复一次中断的流程):
2267
```go
23-
session := blades.NewSession(states)
68+
session := blades.NewSession(map[string]any{
69+
"draft": "Climate change refers to long-term shifts in temperatures and weather patterns...",
70+
})
2471
```
25-
其中states的类型为 **`map[string]any`** 。能够在 **`Session`** 中导入多个 **`State`** 内容。
26-
### Session示例
27-
在blades中使用 **`Session`** 时,只需要在 **`NewRunner`** 方法中传入 **`Session`** 参数即可。
72+
73+
### 将 Session 注入 Runner:让同一链路共享 State
74+
只有把 session 注入运行(blades.WithSession(session)),你前面说的 State 才会在该次运行链路里共享起来。
75+
76+
- 如果你用 runner.Run(...):把 blades.WithSession(session) 作为 option 传入即可。
77+
- 如果你用 runner.RunStream(...):同样可以传入 session option。
78+
79+
## 完整示例:Writer/Reviewer 在 Loop 中共享 draft & suggestions
80+
81+
你的代码本质是一个“写作-审阅”闭环:
82+
1. WriterAgent 生成草稿 → 写入 draft
83+
2. ReviewerAgent 给建议 → 写入 suggestions
84+
3. Loop 条件检查:如果 reviewer 认为 “draft is good” 则停止,否则继续迭代
85+
4. 下一轮 WriterAgent 会在 instruction 里读到 draft 与 suggestions,进行定向改写
86+
2887
```go
2988
package main
3089

3190
import (
3291
"context"
3392
"log"
93+
"os"
94+
"strings"
3495

3596
"github.com/go-kratos/blades"
3697
"github.com/go-kratos/blades/contrib/openai"
98+
"github.com/go-kratos/blades/flow"
3799
)
38100

39101
func main() {
40-
// Configure OpenAI API key and base URL using environment variables:
41-
model := openai.NewModel("gpt-5", openai.Config{
102+
model := openai.NewModel(os.Getenv("OPENAI_MODEL"), openai.Config{
42103
APIKey: os.Getenv("OPENAI_API_KEY"),
43104
})
44-
agent, err := blades.NewAgent(
45-
"History Tutor",
105+
writerAgent, err := blades.NewAgent(
106+
"WriterAgent",
46107
blades.WithModel(model),
47-
blades.WithInstruction("You are a knowledgeable history tutor. Provide detailed and accurate information on historical events."),
108+
blades.WithInstruction(`Draft a short paragraph on climate change.
109+
{{if .suggestions}}
110+
**Draft**
111+
{{.draft}}
112+
113+
Here are the suggestions to consider:
114+
{{.suggestions}}
115+
{{end}}
116+
`),
117+
blades.WithOutputKey("draft"),
48118
)
49119
if err != nil {
50120
log.Fatal(err)
51121
}
52-
input := blades.UserMessage("Can you tell me about the causes of World War II?")
53-
// Create a new session
54-
session := blades.NewSession()
55-
// Run the agent
56-
runner := blades.NewRunner(agent)
57-
output, err := runner.Run(context.Background(), input, blades.WithSession(session))
122+
reviewerAgent, err := blades.NewAgent(
123+
"ReviewerAgent",
124+
blades.WithModel(model),
125+
blades.WithInstruction(`Review the draft and suggest improvements.
126+
If the draft is good, respond with "The draft is good".
127+
128+
**Draft**
129+
{{.draft}}
130+
`),
131+
blades.WithOutputKey("suggestions"),
132+
)
58133
if err != nil {
59134
log.Fatal(err)
60135
}
61-
log.Println(output.Text())
136+
loopAgent := flow.NewLoopAgent(flow.LoopConfig{
137+
Name: "WritingReviewFlow",
138+
Description: "An agent that loops between writing and reviewing until the draft is good.",
139+
MaxIterations: 3,
140+
Condition: func(ctx context.Context, output *blades.Message) (bool, error) {
141+
return !strings.Contains(output.Text(), "The draft is good"), nil
142+
},
143+
SubAgents: []blades.Agent{
144+
writerAgent,
145+
reviewerAgent,
146+
},
147+
})
148+
input := blades.UserMessage("Please write a short paragraph about climate change.")
149+
runner := blades.NewRunner(loopAgent)
150+
stream := runner.RunStream(context.Background(), input)
151+
for message, err := range stream {
152+
if err != nil {
153+
log.Fatal(err)
154+
}
155+
log.Println(message.Author, message.Text())
156+
}
62157
}
63158
```
159+
160+
## 最佳实践
161+
- key 命名要稳定、可读:如 draft、suggestions,复杂项目建议用层级:writing.draft、review.suggestions
162+
- 避免把大段历史对话全塞进 State:State 更适合“结构化/可复用中间产物”,历史对话建议走模型消息或摘要
163+
- 流式输出也要带 session:只要希望多步共享状态,就让整个 Run/RunStream 在同一个 session 下执行

0 commit comments

Comments
 (0)