diff --git a/compose/chain/main.go b/compose/chain/main.go
index 3d7bddb..97b7348 100644
--- a/compose/chain/main.go
+++ b/compose/chain/main.go
@@ -31,6 +31,7 @@ import (
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/cozeloop-go"
+ "github.com/cloudwego/eino-examples/devops/visualize"
"github.com/cloudwego/eino-examples/internal/gptr"
"github.com/cloudwego/eino-examples/internal/logs"
)
@@ -142,7 +143,7 @@ func main() {
}))
// compile
- r, err := chain.Compile(ctx)
+ r, err := chain.Compile(ctx, compose.WithGraphCompileCallbacks(visualize.NewMermaidGenerator("compose/chain")), compose.WithGraphName("chain"))
if err != nil {
log.Panic(err)
return
diff --git a/compose/graph/simple/graph.go b/compose/graph/simple/graph.go
index 9122595..b3d15e4 100644
--- a/compose/graph/simple/graph.go
+++ b/compose/graph/simple/graph.go
@@ -29,6 +29,8 @@ import (
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/cozeloop-go"
+
+ "github.com/cloudwego/eino-examples/devops/visualize"
)
const (
@@ -68,11 +70,18 @@ func main() {
_ = g.AddEdge(nodeOfPrompt, nodeOfModel)
_ = g.AddEdge(nodeOfModel, compose.END)
- r, err := g.Compile(ctx, compose.WithMaxRunSteps(10))
+ gen := visualize.NewMermaidGenerator("compose/graph/simple")
+ r, err := g.Compile(ctx,
+ compose.WithMaxRunSteps(10),
+ compose.WithGraphCompileCallbacks(gen),
+ compose.WithGraphName("SimpleGraph"),
+ )
if err != nil {
panic(err)
}
+ // Mermaid markdown and images are auto-generated in compose/graph/simple
+
in := map[string]any{"location": "beijing"}
ret, err := r.Invoke(ctx, in)
if err != nil {
diff --git a/compose/workflow/4_control_only_branch/main.go b/compose/workflow/4_control_only_branch/main.go
index 0d9d2e6..6d6e8a2 100644
--- a/compose/workflow/4_control_only_branch/main.go
+++ b/compose/workflow/4_control_only_branch/main.go
@@ -25,6 +25,7 @@ import (
"github.com/cloudwego/eino/compose"
"github.com/coze-dev/cozeloop-go"
+ "github.com/cloudwego/eino-examples/devops/visualize"
"github.com/cloudwego/eino-examples/internal/logs"
)
@@ -90,12 +91,15 @@ func main() {
wf.End().AddInput("b1", compose.ToField("bidder1")).
AddInput("b2", compose.ToField("bidder2"))
- runner, err := wf.Compile(context.Background())
+ gen := visualize.NewMermaidGenerator("compose/workflow/4_control_only_branch")
+ runner, err := wf.Compile(context.Background(), compose.WithGraphCompileCallbacks(gen), compose.WithGraphName("Workflow-Control-Only-Branch"))
if err != nil {
logs.Errorf("workflow compile error: %v", err)
return
}
+ // Mermaid markdown and images are auto-generated in compose/workflow/4_control_only_branch
+
result, err := runner.Invoke(context.Background(), 3.0)
if err != nil {
logs.Errorf("workflow run err: %v", err)
diff --git a/devops/visualize/mermaid.go b/devops/visualize/mermaid.go
new file mode 100644
index 0000000..6b3163f
--- /dev/null
+++ b/devops/visualize/mermaid.go
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2025 CloudWeGo Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package visualize
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/chromedp/chromedp"
+ "github.com/cloudwego/eino/compose"
+)
+
+// MermaidGenerator renders a Mermaid diagram from a compiled Eino graph (Graph/Chain/Workflow).
+//
+// Core concepts and mapping:
+// - Nodes: labeled with their key and component type. Lambda nodes use rounded shapes.
+// - Special nodes: START/END are rendered with safe IDs (start_node/end_node) to avoid Mermaid keyword conflicts.
+// - SubGraphs: nested Graph/Chain/Workflow are rendered as Mermaid sub-graphs with their component type in the title.
+// - Edges:
+// - In general graphs/chains: a single solid arrow (-->), representing standard control+data execution.
+// - In workflows (workflowStyle=true): edges are distinguished by semantics:
+// - control+data: normal arrow with label "control+data" ("-- control+data -->")
+// - control-only: bold arrow with label "control-only" ("== control-only ==>")
+// - data-only: dotted arrow with label "data-only" ("-. data-only .->")
+// Branch decision diamonds and their incoming/outgoing edges are treated as control-only in workflows.
+//
+// Usage:
+//
+// buf := &bytes.Buffer{}
+// gen := visualize.NewMermaidGenerator(buf) // for Graph/Chain
+// // or
+// gen := visualize.NewMermaidGeneratorWorkflow(buf) // for Workflow with labeled edges
+// _, _ = g.Compile(ctx, compose.WithGraphCompileCallbacks(gen), compose.WithGraphName("MyGraph"))
+// // Write to a Markdown file:
+// md := "```mermaid\n" + buf.String() + "\n```\n"
+// _ = os.WriteFile("my_graph.md", []byte(md), 0644)
+type MermaidGenerator struct {
+ w io.Writer
+ workflowStyle bool
+ autoWrite bool
+ outDir string
+ baseName string
+ makeImages bool
+}
+
+// NewMermaidGenerator creates a generator that auto-writes Markdown and attempts PNG/SVG generation.
+// If dir is empty, current working directory is used. File name is derived from graph name or defaults to "topology".
+func NewMermaidGenerator(dir string) *MermaidGenerator {
+ return &MermaidGenerator{autoWrite: true, outDir: dir, makeImages: true}
+}
+
+// OnFinish is the compile callback entrypoint invoked by Eino after graph compilation.
+// It reads the compile-time GraphInfo and writes a complete Mermaid diagram to the writer.
+func (m *MermaidGenerator) OnFinish(_ context.Context, info *compose.GraphInfo) {
+ m.generate(info)
+}
+
+// generate orchestrates diagram construction by delegating to renderGraph.
+// The top-level direction is TD (top-down) for readability and consistency.
+func (m *MermaidGenerator) generate(info *compose.GraphInfo) {
+ var isWorkflow bool
+ if len(info.Edges) > len(info.DataEdges) {
+ isWorkflow = true
+ }
+
+ if !isWorkflow {
+ for from, edges := range info.Edges {
+ dataEdges, ok := info.DataEdges[from]
+ if !ok {
+ isWorkflow = true
+ break
+ }
+
+ if len(edges) != len(dataEdges) {
+ isWorkflow = true
+ break
+ }
+
+ for i := range edges {
+ edge := edges[i]
+ found := false
+ for _, dEdge := range dataEdges {
+ if dEdge == edge {
+ found = true
+ break
+ }
+ }
+ if !found {
+ isWorkflow = true
+ break
+ }
+ }
+ }
+ }
+
+ sb := &strings.Builder{}
+ sb.WriteString("graph TD\n")
+ m.renderGraph(sb, info, "", 1, isWorkflow)
+ if m.w != nil && !m.autoWrite {
+ _, _ = fmt.Fprint(m.w, sb.String())
+ return
+ }
+
+ dir := m.outDir
+ if dir == "" {
+ if wd, err := os.Getwd(); err == nil {
+ dir = wd
+ } else {
+ dir = "."
+ }
+ }
+ name := m.baseName
+ if name == "" {
+ if len(info.Name) > 0 {
+ name = sanitize(info.Name)
+ } else {
+ name = "topology"
+ }
+ }
+ mdPath := filepath.Join(dir, name+".md")
+ content := sb.String()
+ _ = os.WriteFile(mdPath, []byte("```mermaid\n"+content+"\n```"), 0644)
+ if m.makeImages {
+ mmdPath := filepath.Join(dir, name+".mmd")
+ _ = os.WriteFile(mmdPath, []byte(content), 0644)
+ m.renderImage(mmdPath, filepath.Join(dir, name+".png"))
+ _ = os.Remove(mmdPath)
+ }
+}
+
+// renderGraph builds a Mermaid diagram section for the given GraphInfo.
+// It:
+// 1) Collects and sorts node keys for deterministic output
+// 2) Renders nodes (including nested sub-graphs)
+// 3) Renders control/data edges with workflow-aware styles
+// 4) Renders branches with decision diamonds and proper edge types
+func (m *MermaidGenerator) renderGraph(sb *strings.Builder, info *compose.GraphInfo, prefix string, indentLevel int, style bool) {
+ indent := strings.Repeat(" ", indentLevel)
+
+ // Collect all nodes from info.Nodes, Edges, and Branches
+ allNodes := make(map[string]bool)
+ for k := range info.Nodes {
+ allNodes[k] = true
+ }
+ for start, ends := range info.Edges {
+ allNodes[start] = true
+ for _, end := range ends {
+ allNodes[end] = true
+ }
+ }
+ for start, branches := range info.Branches {
+ allNodes[start] = true
+ for _, branch := range branches {
+ endNodes := branch.GetEndNode()
+ for end := range endNodes {
+ allNodes[end] = true
+ }
+ }
+ }
+
+ // Sort nodes for deterministic output
+ nodes := make([]string, 0, len(allNodes))
+ for k := range allNodes {
+ nodes = append(nodes, k)
+ }
+ sort.Strings(nodes)
+
+ // Render Nodes
+ for _, nodeKey := range nodes {
+ nodeID := m.nodeID(prefix, nodeKey)
+
+ if nodeInfo, ok := info.Nodes[nodeKey]; ok {
+ if nodeInfo.GraphInfo != nil {
+ // Subgraph
+ subgraphLabel := nodeKey
+ if nodeInfo.Component == compose.ComponentOfChain {
+ subgraphLabel = fmt.Sprintf("%s (Chain)", nodeKey)
+ } else if nodeInfo.Component == compose.ComponentOfWorkflow {
+ subgraphLabel = fmt.Sprintf("%s (Workflow)", nodeKey)
+ } else if nodeInfo.Component == compose.ComponentOfGraph {
+ subgraphLabel = fmt.Sprintf("%s (Graph)", nodeKey)
+ }
+ sb.WriteString(fmt.Sprintf("%ssubgraph %s [\"%s\"]\n", indent, nodeID, subgraphLabel))
+ childStyle := style
+ if nodeInfo.Component == compose.ComponentOfWorkflow {
+ childStyle = true
+ } else if nodeInfo.Component == compose.ComponentOfGraph || nodeInfo.Component == compose.ComponentOfChain {
+ // for explicit Graph/Chain sub-graphs, do not apply workflow styling
+ childStyle = false
+ }
+ m.renderGraph(sb, nodeInfo.GraphInfo, nodeID+"_", indentLevel+1, childStyle)
+ sb.WriteString(fmt.Sprintf("%send\n", indent))
+ } else {
+ // Regular Node
+ shapeStart, shapeEnd := "[", "]"
+ if nodeInfo.Component == compose.ComponentOfLambda {
+ shapeStart, shapeEnd = "(", ")"
+ }
+
+ label := fmt.Sprintf("%s
(%s)", nodeKey, nodeInfo.Component)
+ sb.WriteString(fmt.Sprintf("%s%s%s\"%s\"%s\n", indent, nodeID, shapeStart, label, shapeEnd))
+ }
+ } else if nodeKey == compose.START || nodeKey == compose.END {
+ // Special nodes: avoid reserved keyword conflict with 'end'
+ safeID := nodeID
+ if nodeKey == compose.START {
+ safeID = m.nodeID(prefix, "start_node")
+ } else {
+ safeID = m.nodeID(prefix, "end_node")
+ }
+ sb.WriteString(fmt.Sprintf("%s%s([%s])\n", indent, safeID, nodeKey))
+ }
+ }
+
+ // Render Control Edges
+ // Sort edges for deterministic output
+ startNodes := make([]string, 0, len(info.Edges))
+ for k := range info.Edges {
+ startNodes = append(startNodes, k)
+ }
+ sort.Strings(startNodes)
+
+ for _, start := range startNodes {
+ ends := info.Edges[start]
+ for _, end := range ends {
+ startID := m.nodeID(prefix, start)
+ endID := m.nodeID(prefix, end)
+ if start == compose.START {
+ startID = m.nodeID(prefix, "start_node")
+ }
+ if end == compose.END {
+ endID = m.nodeID(prefix, "end_node")
+ }
+ // Determine edge semantics by checking if a matching data edge exists.
+ hasData := false
+ if des, ok := info.DataEdges[start]; ok {
+ for _, de := range des {
+ if de == end {
+ hasData = true
+ break
+ }
+ }
+ }
+ if style {
+ if hasData {
+ sb.WriteString(fmt.Sprintf("%s%s -- control+data --> %s\n", indent, startID, endID))
+ } else {
+ sb.WriteString(fmt.Sprintf("%s%s == control-only ==> %s\n", indent, startID, endID))
+ }
+ } else {
+ sb.WriteString(fmt.Sprintf("%s%s --> %s\n", indent, startID, endID))
+ }
+ }
+ }
+
+ // Render Data Edges
+ // Only render if they differ from control edges; otherwise already represented as control+data.
+ dataStartNodes := make([]string, 0, len(info.DataEdges))
+ for k := range info.DataEdges {
+ dataStartNodes = append(dataStartNodes, k)
+ }
+ sort.Strings(dataStartNodes)
+
+ for _, start := range dataStartNodes {
+ ends := info.DataEdges[start]
+ for _, end := range ends {
+ // Check if this edge already exists as a control edge
+ alreadyExists := false
+ for _, controlEnd := range info.Edges[start] {
+ if controlEnd == end {
+ alreadyExists = true
+ break
+ }
+ }
+ if !alreadyExists {
+ startID := m.nodeID(prefix, start)
+ endID := m.nodeID(prefix, end)
+ if start == compose.START {
+ startID = m.nodeID(prefix, "start_node")
+ }
+ if end == compose.END {
+ endID = m.nodeID(prefix, "end_node")
+ }
+ if style {
+ sb.WriteString(fmt.Sprintf("%s%s -. data-only .-> %s\n", indent, startID, endID))
+ } else {
+ sb.WriteString(fmt.Sprintf("%s%s -.-> %s\n", indent, startID, endID))
+ }
+ }
+ }
+ }
+
+ // Render Branches
+ branchStarts := make([]string, 0, len(info.Branches))
+ for k := range info.Branches {
+ branchStarts = append(branchStarts, k)
+ }
+ sort.Strings(branchStarts)
+
+ for _, start := range branchStarts {
+ branches := info.Branches[start]
+ for i, branch := range branches {
+ // Branch decision node (diamond)
+ // We need a unique ID for the decision point if there are multiple branches from the same node?
+ // Actually, `info.Branches` maps startNode -> []GraphBranch.
+ // Usually a node has one set of branches.
+ // Let's represent the branch condition as a diamond.
+
+ // If there are multiple branches, they might be parallel or sequential conditions.
+ // Eino `AddBranch` adds a branch.
+
+ // For visualization, maybe we just draw arrows from startNode to endNodes with a label?
+ // Or introduce a "decision" node?
+
+ // Decision node visualization: startNode -> decision{branch} -> endNodes
+
+ decisionID := fmt.Sprintf("%s_branch_%d", m.nodeID(prefix, start), i)
+ sb.WriteString(fmt.Sprintf("%s%s{\"%s\"}\n", indent, decisionID, "branch"))
+ startID := m.nodeID(prefix, start)
+ if start == compose.START {
+ startID = m.nodeID(prefix, "start_node")
+ }
+ if style {
+ sb.WriteString(fmt.Sprintf("%s%s ==> %s\n", indent, startID, decisionID))
+ } else {
+ sb.WriteString(fmt.Sprintf("%s%s --> %s\n", indent, startID, decisionID))
+ }
+
+ // Sort end nodes
+ endNodesMap := branch.GetEndNode()
+ endNodes := make([]string, 0, len(endNodesMap))
+ for k := range endNodesMap {
+ endNodes = append(endNodes, k)
+ }
+ sort.Strings(endNodes)
+
+ for _, end := range endNodes {
+ endID := m.nodeID(prefix, end)
+ if end == compose.END {
+ endID = m.nodeID(prefix, "end_node")
+ }
+ if style {
+ sb.WriteString(fmt.Sprintf("%s%s ==> %s\n", indent, decisionID, endID))
+ } else {
+ sb.WriteString(fmt.Sprintf("%s%s --> %s\n", indent, decisionID, endID))
+ }
+ }
+ }
+ }
+}
+
+// nodeID sanitizes a node key to be a valid Mermaid identifier, and adds a caller-provided prefix
+// to ensure uniqueness when rendering nested graphs.
+func (m *MermaidGenerator) nodeID(prefix, key string) string {
+ // Sanitize key for Mermaid ID
+ safeKey := strings.ReplaceAll(key, " ", "_")
+ safeKey = strings.ReplaceAll(safeKey, "-", "_")
+ return prefix + safeKey
+}
+
+func (m *MermaidGenerator) renderImage(input, output string) {
+ if _, err := exec.LookPath("mmdc"); err != nil {
+ // fallback to chromedp rendering
+ data, rErr := os.ReadFile(input)
+ if rErr != nil {
+ return
+ }
+ _ = renderWithChromedp(string(data), output)
+ return
+ }
+ cmd := exec.Command("mmdc", "-i", input, "-o", output)
+ _ = cmd.Run()
+}
+
+func sanitize(s string) string {
+ s = strings.TrimSpace(s)
+ s = strings.ReplaceAll(s, " ", "_")
+ s = strings.ReplaceAll(s, "/", "_")
+ s = strings.ReplaceAll(s, "\\", "_")
+ return s
+}
+
+func renderWithChromedp(mermaidCode, output string) error {
+ html := buildMermaidHTML(mermaidCode)
+ ctx, cancel := chromedp.NewContext(context.Background())
+ defer cancel()
+
+ var err error
+ ext := strings.ToLower(filepath.Ext(output))
+ switch ext {
+ case ".png":
+ var buf []byte
+ err = chromedp.Run(ctx,
+ chromedp.Navigate("data:text/html,"+urlEncode(html)),
+ chromedp.WaitVisible(`#container svg`, chromedp.ByQuery),
+ chromedp.Screenshot(`#container svg`, &buf, chromedp.NodeVisible, chromedp.ByQuery),
+ )
+ if err == nil {
+ err = os.WriteFile(output, buf, 0644)
+ }
+ case ".svg":
+ var svg string
+ err = chromedp.Run(ctx,
+ chromedp.Navigate("data:text/html,"+urlEncode(html)),
+ chromedp.WaitVisible(`#container svg`, chromedp.ByQuery),
+ chromedp.OuterHTML(`#container svg`, &svg, chromedp.ByQuery),
+ )
+ if err == nil {
+ err = os.WriteFile(output, []byte(svg), 0644)
+ }
+ default:
+ // unsupported extension
+ return nil
+ }
+ return err
+}
+
+func buildMermaidHTML(code string) string {
+ return `
+
+
+
+
+
+
+
+
+ ` + code + `
+
+`
+}
+
+func urlEncode(s string) string { // minimal percent-encoding for data URL
+ r := strings.NewReplacer("%", "%25", "#", "%23", "\n", "%0A", "\r", "%0D")
+ return r.Replace(s)
+}
diff --git a/flow/agent/multiagent/host/journal/main.go b/flow/agent/multiagent/host/journal/main.go
index e22ae1b..e3c7a15 100644
--- a/flow/agent/multiagent/host/journal/main.go
+++ b/flow/agent/multiagent/host/journal/main.go
@@ -22,8 +22,11 @@ import (
"io"
"os"
+ "github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/flow/agent/multiagent/host"
"github.com/cloudwego/eino/schema"
+
+ "github.com/cloudwego/eino-examples/devops/visualize"
)
func main() {
@@ -68,6 +71,17 @@ func main() {
panic(err)
}
+ // export the graph via API and render mermaid (non-critical path)
+ {
+ anyG, opts := hostMA.ExportGraph()
+ gen := visualize.NewMermaidGenerator("flow/agent/multiagent/host/journal")
+ g := compose.NewGraph[[]*schema.Message, *schema.Message]()
+ _ = g.AddGraphNode("host_multiagent", anyG, opts...)
+ _ = g.AddEdge(compose.START, "host_multiagent")
+ _ = g.AddEdge("host_multiagent", compose.END)
+ _, _ = g.Compile(context.Background(), compose.WithGraphCompileCallbacks(gen))
+ }
+
cb := &logCallback{}
for { // 多轮对话,除非用户输入了 "exit",否则一直循环
diff --git a/flow/agent/multiagent/plan_execute/compose.go b/flow/agent/multiagent/plan_execute/compose.go
index 0888e4d..fadfbe3 100644
--- a/flow/agent/multiagent/plan_execute/compose.go
+++ b/flow/agent/multiagent/plan_execute/compose.go
@@ -27,6 +27,7 @@ import (
"github.com/cloudwego/eino/flow/agent"
"github.com/cloudwego/eino/schema"
+ "github.com/cloudwego/eino-examples/devops/visualize"
"github.com/cloudwego/eino-examples/flow/agent/multiagent/plan_execute/prompt"
)
@@ -196,12 +197,20 @@ func NewMultiAgent(ctx context.Context, config *Config) (*PlanExecuteMultiAgent,
}))
_ = graph.AddEdge(nodeKeyReviserToList, nodeKeyExecutor)
- // 编译 graph,将节点、边、分支转化为面向运行时的结构。由于 graph 中存在环,使用 AnyPredecessor 模式,同时设置运行时最大步数。
- runnable, err := graph.Compile(ctx, compose.WithNodeTriggerMode(compose.AnyPredecessor), compose.WithMaxRunSteps(maxStep))
+ // 编译 graph,并生成 Mermaid 拓扑图。由于 graph 中存在环,使用 AnyPredecessor 模式,同时设置运行时最大步数。
+ gen := visualize.NewMermaidGenerator("flow/agent/multiagent/plan_execute")
+ runnable, err := graph.Compile(ctx,
+ compose.WithNodeTriggerMode(compose.AnyPredecessor),
+ compose.WithMaxRunSteps(maxStep),
+ compose.WithGraphCompileCallbacks(gen),
+ compose.WithGraphName("PlanExecuteMultiAgent"),
+ )
if err != nil {
return nil, err
}
+ // Mermaid markdown and images are auto-generated in flow/agent/multiagent/plan_execute
+
return &PlanExecuteMultiAgent{
runnable: runnable,
}, nil
diff --git a/flow/agent/multiagent/plan_execute/main.go b/flow/agent/multiagent/plan_execute/main.go
index f015f33..a5eab09 100644
--- a/flow/agent/multiagent/plan_execute/main.go
+++ b/flow/agent/multiagent/plan_execute/main.go
@@ -27,8 +27,6 @@ import (
"time"
"github.com/bytedance/sonic"
- "github.com/cloudwego/eino-examples/flow/agent/multiagent/plan_execute/debug"
- "github.com/cloudwego/eino-examples/flow/agent/multiagent/plan_execute/tools"
clc "github.com/cloudwego/eino-ext/callbacks/cozeloop"
"github.com/cloudwego/eino-ext/components/model/ark"
"github.com/cloudwego/eino-ext/components/model/deepseek"
@@ -40,6 +38,9 @@ import (
"github.com/cloudwego/eino/schema"
"github.com/cloudwego/eino/utils/callbacks"
"github.com/coze-dev/cozeloop-go"
+
+ "github.com/cloudwego/eino-examples/flow/agent/multiagent/plan_execute/debug"
+ "github.com/cloudwego/eino-examples/flow/agent/multiagent/plan_execute/tools"
)
func main() {
diff --git a/flow/agent/react/react.go b/flow/agent/react/react.go
index fe01979..eaa339b 100644
--- a/flow/agent/react/react.go
+++ b/flow/agent/react/react.go
@@ -35,6 +35,7 @@ import (
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/cozeloop-go"
+ "github.com/cloudwego/eino-examples/devops/visualize"
"github.com/cloudwego/eino-examples/flow/agent/react/tools"
"github.com/cloudwego/eino-examples/internal/logs"
)
@@ -60,14 +61,15 @@ func main() {
}
callbacks.AppendGlobalHandlers(handlers...)
- config := &ark.ChatModelConfig{
- APIKey: arkApiKey,
- Model: arkModelName,
- }
+ // minimal: we will export graph via API when available and compile a mermaid diagram
// Create a new cached ark chat model.
//arkModel, err = NewCachedARKChatModel(ctx, config)
+ config := &ark.ChatModelConfig{
+ APIKey: arkApiKey,
+ Model: arkModelName,
+ }
arkModel, err := ark.NewChatModel(ctx, config)
if err != nil {
logs.Errorf("failed to create chat model: %v", err)
@@ -75,8 +77,8 @@ func main() {
}
// prepare tools
- restaurantTool := tools.GetRestaurantTool() // 查询餐厅信息的工具
- dishTool := tools.GetDishTool() // 查询餐厅菜品信息的工具
+ restaurantTool := tools.GetRestaurantTool()
+ dishTool := tools.GetDishTool()
// prepare persona (system prompt) (optional)
persona := `# Character:
@@ -106,7 +108,7 @@ func main() {
return false, nil
}*/
- ragent, err := react.NewAgent(ctx, &react.AgentConfig{
+ rAgent, err := react.NewAgent(ctx, &react.AgentConfig{
ToolCallingModel: arkModel,
ToolsConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{restaurantTool, dishTool},
@@ -145,7 +147,18 @@ func main() {
//react.WithChatModelOptions(ark.WithCache(cacheOption)),
}
- sr, err := ragent.Stream(ctx, []*schema.Message{
+ // Export graph and compile with mermaid (non-critical path)
+ {
+ anyG, opts := rAgent.ExportGraph()
+ gen := visualize.NewMermaidGenerator("flow/agent/react")
+ g := compose.NewGraph[[]*schema.Message, *schema.Message]()
+ _ = g.AddGraphNode("react_agent", anyG, opts...)
+ _ = g.AddEdge(compose.START, "react_agent")
+ _ = g.AddEdge("react_agent", compose.END)
+ _, _ = g.Compile(context.Background(), compose.WithGraphCompileCallbacks(gen))
+ }
+
+ sr, err := rAgent.Stream(ctx, []*schema.Message{
{
Role: schema.System,
Content: persona,
diff --git a/go.mod b/go.mod
index 879568f..80b6b89 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ toolchain go1.24.9
require (
github.com/bytedance/sonic v1.14.2
+ github.com/chromedp/chromedp v0.9.5
github.com/cloudwego/eino v0.7.0
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.6
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20251117090452-bd6375a0b3cf
@@ -24,7 +25,7 @@ require (
github.com/google/uuid v1.6.0
github.com/json-iterator/go v1.1.12
github.com/kaptinlin/jsonrepair v0.2.4
- github.com/ollama/ollama v0.11.4
+ github.com/pkoukk/tiktoken-go v0.1.8
github.com/stretchr/testify v1.11.1
github.com/volcengine/volcengine-go-sdk v1.1.44
github.com/wk8/go-ordered-map/v2 v2.1.8
@@ -42,6 +43,8 @@ require (
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732 // indirect
+ github.com/chromedp/sysutil v1.0.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2 // indirect
github.com/cohesion-org/deepseek-go v1.3.2 // indirect
@@ -52,14 +55,13 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eino-contrib/ollama v0.1.0 // indirect
github.com/evanphx/json-patch v0.5.2 // indirect
- github.com/getkin/kin-openapi v0.118.0 // indirect
- github.com/go-openapi/jsonpointer v0.21.1 // indirect
- github.com/go-openapi/swag v0.23.1 // indirect
+ github.com/gobwas/httphead v0.1.0 // indirect
+ github.com/gobwas/pool v0.2.1 // indirect
+ github.com/gobwas/ws v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/goph/emperror v0.17.2 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/mux v1.8.1 // indirect
- github.com/invopop/yaml v0.3.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
@@ -71,14 +73,12 @@ require (
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/nikolalohinski/gonja v1.5.3 // indirect
github.com/nikolalohinski/gonja/v2 v2.3.1 // indirect
+ github.com/ollama/ollama v0.11.4 // indirect
github.com/openai/openai-go v1.10.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
- github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/pkoukk/tiktoken-go v0.1.8 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
@@ -90,7 +90,6 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tiendc/go-deepcopy v1.7.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
- github.com/ugorji/go/codec v1.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/volcengine/volc-sdk-golang v1.0.199 // indirect
diff --git a/go.sum b/go.sum
index 0d3b0ca..ffc9381 100644
--- a/go.sum
+++ b/go.sum
@@ -104,6 +104,12 @@ github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEex
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732 h1:XYUCaZrW8ckGWlCRJKCSoh/iFwlpX316a8yY9IFEzv8=
+github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
+github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
+github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
+github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
+github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -113,64 +119,30 @@ github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5P
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
-github.com/cloudwego/eino v0.5.14 h1:cthGepU40W75YXYGjpbfakVHrsBOXJhqXVLLcpY+UqM=
-github.com/cloudwego/eino v0.5.14/go.mod h1:N6E+toMzWw/3ql0IVM5n5lbYFCeblCYx7ebH16kt1JQ=
-github.com/cloudwego/eino v0.6.2-0.20251119064845-207bfdd551a5 h1:wMWSasWJf7/VSe+AA4ZBgnP9Si6zEqZfAB86wtZwszg=
-github.com/cloudwego/eino v0.6.2-0.20251119064845-207bfdd551a5/go.mod h1:JNapfU+QUrFFpboNDrNOFvmz0m9wjBFHHCr77RH6a50=
-github.com/cloudwego/eino v0.6.2-0.20251120035928-4a4f0c16bdd3 h1:dUQXYicmUNZO/xM8wMKdICGJervM2JGwL37E92wVuW4=
-github.com/cloudwego/eino v0.6.2-0.20251120035928-4a4f0c16bdd3/go.mod h1:JNapfU+QUrFFpboNDrNOFvmz0m9wjBFHHCr77RH6a50=
-github.com/cloudwego/eino v0.6.2-0.20251120060359-fd8688de61e0 h1:UG+RnyF4kkD3G7W86JuuCcUUM2wE1VhxCVykqd3yo4c=
-github.com/cloudwego/eino v0.6.2-0.20251120060359-fd8688de61e0/go.mod h1:JNapfU+QUrFFpboNDrNOFvmz0m9wjBFHHCr77RH6a50=
-github.com/cloudwego/eino v0.6.2-0.20251120060852-4c29682b927c h1:XupuM1TJ8qEzADtqAnNkmZxbejg8kKvEzTlADOCcpfE=
-github.com/cloudwego/eino v0.6.2-0.20251120060852-4c29682b927c/go.mod h1:JNapfU+QUrFFpboNDrNOFvmz0m9wjBFHHCr77RH6a50=
github.com/cloudwego/eino v0.7.0 h1:XDGdGMZCAVx+OC0IxiLlyNFELoLN+56THUhYYqEujuM=
github.com/cloudwego/eino v0.7.0/go.mod h1:JNapfU+QUrFFpboNDrNOFvmz0m9wjBFHHCr77RH6a50=
-github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.4 h1:Cwm80+fMEAoY7uI9XUukDEV5J3Adb9HiXKmcXpXcDiM=
-github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.4/go.mod h1:xxjNsJeZwdIooEYTt6tjw/YJzkA6xPcxn1u/HvA5xc0=
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.6 h1:gS4nAOpQQC5WItt1k32yjZt9O2UWMpnbgF6vkMQAWhg=
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.6/go.mod h1:ZniRkgN+9FUFxtN60X7yzD6UOruqrKQusjrOiGcH4I8=
-github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250117061805-cd80d1780d76 h1:kK4f2kunb5xlc0XTkg6wkjy8Z/BDfJjWAVm9EOdRErg=
-github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250117061805-cd80d1780d76/go.mod h1:LWR+h0EfIELl/I1tDSVH0Tgx8j2gymxa174U1C8BNps=
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20251117090452-bd6375a0b3cf h1:Uwh3VT+xPrfDjM677dj1pSidCzBFoTrYlC274kEci5w=
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20251117090452-bd6375a0b3cf/go.mod h1:DBwsPrdNxPeE3HNr5XyjjFreG4ypqCUjN0T2C2JSy6k=
-github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250117061805-cd80d1780d76 h1:GJ4OqxyBH8la8Gu4PhTHXZNZFmrtEIrrymkCEpJ7XZU=
-github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250117061805-cd80d1780d76/go.mod h1:swAgO0nNekTSKGgFqiy4zShKaCDhiIZoKEFwpi7NBFE=
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20251117090452-bd6375a0b3cf h1:0KFSxuvFqs9dJ3Pu9Lk+4+Stt43I2u9eu3L4gDNcZ4A=
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20251117090452-bd6375a0b3cf/go.mod h1:kHC3xkGM/gv3IHpOk33p75BfBaEIYATOs2XmYFKffcs=
-github.com/cloudwego/eino-ext/components/model/ark v0.1.41 h1:l+WDY/nR1A5CzNkOJ+394EY+TxaW+NC5if1gnwcsvtE=
-github.com/cloudwego/eino-ext/components/model/ark v0.1.41/go.mod h1:RIJTJsjS1Z1Xrldk6oeIkM0IHFasmYVfPh4b6ceExpY=
github.com/cloudwego/eino-ext/components/model/ark v0.1.45 h1:LWvSHJVlvS1S/IxN9XUKNw/MI0I7YPePt3LMNxyCrZ0=
github.com/cloudwego/eino-ext/components/model/ark v0.1.45/go.mod h1:e8P5dGVI/JMQ1FYNgmu5EFRWA8fivBc6NwNJ9g8FBK8=
-github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250826125654-37d4a5029810 h1:zichoSWCoGqhUUwWscRNiTSH9j3KA8hReeEBMvr4i5w=
-github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20250826125654-37d4a5029810/go.mod h1:O6leaZBwE5s1wSsf5idW52Yaj1zWzhtjinSnfPXASNU=
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20251117090452-bd6375a0b3cf h1:JA2W09VU+9zG9qRXheARRbi31cH+xHVzSJ/kFMxZvlo=
github.com/cloudwego/eino-ext/components/model/deepseek v0.0.0-20251117090452-bd6375a0b3cf/go.mod h1:3AlAYkxwlOAP0SnFj7Yu1Lsgp8XHpbSMaE3l0+vDHKQ=
-github.com/cloudwego/eino-ext/components/model/ollama v0.1.2 h1:WxJ+7oXnr3AhM6u4VbFF3L2ionxCrPfmLetx7V+zthw=
-github.com/cloudwego/eino-ext/components/model/ollama v0.1.2/go.mod h1:OgGMCiR/G/RnOWaJvdK8pVSxAzoz2SlCqim43oFTuwo=
github.com/cloudwego/eino-ext/components/model/ollama v0.1.6 h1:ZbrhV91uE0hGIOYXhb2i3G6tQJ/rK2SLYtoYrmocZXM=
github.com/cloudwego/eino-ext/components/model/ollama v0.1.6/go.mod h1:GDXrvorGdRNV6g2mK5jdla2D8Xc/hh7XDrTeGDteLLo=
-github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250826125654-37d4a5029810 h1:M8A7666rddupncJ4p3p1lH5jkNKtjzD7ULPE/I02o64=
-github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250826125654-37d4a5029810/go.mod h1:QQhCuQxuBAVWvu/YAZBhs/RsR76mUigw59Tl0kh04C8=
github.com/cloudwego/eino-ext/components/model/openai v0.1.5 h1:+yvGbTPw93li9GSmdm6Rix88Yy8AXg5NNBcRbWx3CQU=
github.com/cloudwego/eino-ext/components/model/openai v0.1.5/go.mod h1:IPVYMFoZcuHeVEsDTGN6SZjvue0xr1iZFhdpq1SBWdQ=
-github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20250319082935-6219ec437e56 h1:yL7nTthGoz35dh7RK9lazxDedGmSmlss8HrtXmd0O3Q=
-github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20250319082935-6219ec437e56/go.mod h1:OPQYefu4EWAUcP0HhtzJK+UjLtQc8T9YzmxcAwFf29o=
github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20251120060928-25485ef519b5 h1:HoXqcYm3x4eXk6i2HLBklvx8WeFW7/MxijpCsEBQLSE=
github.com/cloudwego/eino-ext/components/retriever/volc_vikingdb v0.0.0-20251120060928-25485ef519b5/go.mod h1:He7AHJpLTs0MXKPx5JpI8MdYtLUcKhUAZwCsgNtaq6k=
-github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251111090228-91a10bbc864f h1:JkELXl5NquNnDgi81T3uiPfgpfcXzH81e3B/NDO4cwM=
-github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251111090228-91a10bbc864f/go.mod h1:WfhOxJY7J2ZqY9muahNOMtypoe4ovcAG1E97y4bQPfU=
github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251117090452-bd6375a0b3cf h1:LLyVGanJpHpSSiqsbRXkuSpdpc1UmuaUIEs/BiOQkH4=
github.com/cloudwego/eino-ext/components/tool/commandline v0.0.0-20251117090452-bd6375a0b3cf/go.mod h1:cMZb1KM71kM+2hTJwxLwXT+HC66HimZirDDA5Oo64Hw=
-github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20250826125654-37d4a5029810 h1:VtmhPdOY6wFR34Hdm4HNzWF9bwGz11x1TqBr8VpJDoc=
-github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20250826125654-37d4a5029810/go.mod h1:QB9TkAu6OVDvQm5hEoK+VFVIbfDHO+ZQyUR2cPSo6jk=
github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20251117090452-bd6375a0b3cf h1:54xcNETtXP1gYieDuNyOIvzE4Mk/jOxjlqCERr4cxTk=
github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2 v2.0.0-20251117090452-bd6375a0b3cf/go.mod h1:Np0BXy/9hPRu3wCgn+ij6L7YsjFcybVzg1k7uYOXh0M=
-github.com/cloudwego/eino-ext/devops v0.1.7 h1:w8q4SGDtrZk+8WUrLyVidX7GNsyWFsYPPz5HoJKgRcA=
-github.com/cloudwego/eino-ext/devops v0.1.7/go.mod h1:W53NPBmkmM7ClyQqwOcFUC2kV4bsvCfh2FtHUcyMzMY=
github.com/cloudwego/eino-ext/devops v0.1.8 h1:qBg5vjZSDnd9tHzCHG8YsjnGB5vKG2EoZuuQCI8qrGs=
github.com/cloudwego/eino-ext/devops v0.1.8/go.mod h1:8yjvPNTaB5Ve4aJmJ0ysFgB10y3YbIuqMh0/Uwt5Fnw=
-github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250826113018-8c6f6358d4bb h1:RMslzyijc3bi9EkqCulpS0hZupTl1y/wayR3+fVRN/c=
-github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250826113018-8c6f6358d4bb/go.mod h1:fHn/6OqPPY1iLLx9wzz+MEVT5Dl9gwuZte1oLEnCoYw=
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2 h1:r9Id2wzJ05PoHl+Km7jQgNMgciaZI93TVnUYso89esM=
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2/go.mod h1:S4OkvglPY9hsm9tXeShODrf/WN1Cgu4bqu4nn/CnIic=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@@ -184,8 +156,6 @@ github.com/corpix/uarand v0.2.0 h1:U98xXwud/AVuCpkpgfPF7J5TQgr7R5tqT8VZP5KWbzE=
github.com/corpix/uarand v0.2.0/go.mod h1:/3Z1QIqWkDIhf6XWn/08/uMHoQ8JUoTIKc2iPchBOmM=
github.com/coze-dev/cozeloop-go v0.1.11 h1:NzdBKK3klhRgnPEH/PCjtP21+cRuz6qZlKPmJC7bkiM=
github.com/coze-dev/cozeloop-go v0.1.11/go.mod h1:GDF+MpqsUsq4oKy+L1FTd6Giy2cAmr/xXl9rOnub39A=
-github.com/coze-dev/cozeloop-go/spec v0.1.4 h1:6yhjFQ39yn9VkZ68QG2W5WjzICl6GgwVsXmDmn75ySg=
-github.com/coze-dev/cozeloop-go/spec v0.1.4/go.mod h1:/f3BrWehffwXIpd4b5rYIqktLd/v5dlLBw0h9F/LQIU=
github.com/coze-dev/cozeloop-go/spec v0.1.5 h1:tEQ82qlz9/HZv8MqyZq+043SaHs5C44MWslyGm5UcNI=
github.com/coze-dev/cozeloop-go/spec v0.1.5/go.mod h1:/f3BrWehffwXIpd4b5rYIqktLd/v5dlLBw0h9F/LQIU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -229,8 +199,6 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM=
-github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
@@ -249,19 +217,17 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
-github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
-github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
-github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
-github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
+github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
+github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
+github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
+github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
+github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -385,9 +351,6 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
-github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
-github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
-github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
@@ -439,8 +402,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
+github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
@@ -461,8 +424,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/meguminnnnnnnnn/go-openai v0.0.0-20250821095446-07791bea23a0 h1:nIohpHs1ViKR0SVgW/cbBstHjmnqFZDM9RqgX9m9Xu8=
-github.com/meguminnnnnnnnn/go-openai v0.0.0-20250821095446-07791bea23a0/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY=
github.com/meguminnnnnnnnn/go-openai v0.1.0 h1:BGzB1PlS2Epq0mBB2TGLwzMihbR7BANrlMH3w4ZnY88=
github.com/meguminnnnnnnnn/go-openai v0.1.0/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
@@ -487,8 +448,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
-github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
@@ -527,14 +486,13 @@ github.com/openai/openai-go v1.10.1 h1:7VR8z1foqJDjlaFZsNH5zZIYTWKYz97tdsVSzXDHQ
github.com/openai/openai-go v1.10.1/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
+github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
+github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM=
-github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
-github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
-github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -618,7 +576,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
@@ -638,18 +595,12 @@ github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTR
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
-github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
-github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
-github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
-github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
-github.com/volcengine/volc-sdk-golang v1.0.196 h1:KYbX76ibTlNhgm6Kq3sDTjrujDBd0za8ULg0v7+sCag=
-github.com/volcengine/volc-sdk-golang v1.0.196/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
github.com/volcengine/volc-sdk-golang v1.0.199 h1:zv9QOqTl/IsLwtfC37GlJtcz6vMAHi+pjq8ILWjLYUc=
github.com/volcengine/volc-sdk-golang v1.0.199/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ=
github.com/volcengine/volcengine-go-sdk v1.1.44 h1:WLoLlzt67ZlJeow55PPx65/Mh52DewVXqkHcFSodM9w=
@@ -901,9 +852,11 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
@@ -1118,7 +1071,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=