|
1 | 1 | --- |
2 | 2 | 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" |
4 | 4 | reference: ["https://github.com/go-kratos/blades/tree/main/examples/state","https://github.com/go-kratos/blades/tree/main/examples/session"] |
5 | 5 | --- |
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 | + |
7 | 7 | ## 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. |
9 | 8 |
|
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 | +``` |
11 | 58 |
|
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 |
13 | 60 |
|
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: |
16 | 63 | ```go |
17 | 64 | session := blades.NewSession() |
18 | | -session.PutState(agent.Name(), output.Text()) |
19 | 65 | ``` |
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): |
22 | 67 | ```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 | +}) |
24 | 71 | ``` |
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 | + |
28 | 87 | ```go |
29 | 88 | package main |
30 | 89 |
|
31 | 90 | import ( |
32 | 91 | "context" |
33 | 92 | "log" |
| 93 | + "os" |
| 94 | + "strings" |
34 | 95 |
|
35 | 96 | "github.com/go-kratos/blades" |
36 | 97 | "github.com/go-kratos/blades/contrib/openai" |
| 98 | + "github.com/go-kratos/blades/flow" |
37 | 99 | ) |
38 | 100 |
|
39 | 101 | 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{ |
42 | 103 | APIKey: os.Getenv("OPENAI_API_KEY"), |
43 | 104 | }) |
44 | | - agent, err := blades.NewAgent( |
45 | | - "History Tutor", |
| 105 | + writerAgent, err := blades.NewAgent( |
| 106 | + "WriterAgent", |
46 | 107 | 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"), |
48 | 118 | ) |
49 | 119 | if err != nil { |
50 | 120 | log.Fatal(err) |
51 | 121 | } |
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 | + ) |
58 | 133 | if err != nil { |
59 | 134 | log.Fatal(err) |
60 | 135 | } |
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 | + } |
62 | 157 | } |
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. |
0 commit comments