feat(adk): deepagents example (#131)

drew/english
Megumin 6 months ago committed by GitHub
parent 550f5b504c
commit 09c7e3515e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,61 @@
## Eino ADK DeepAgents Example: Excel Agent
### Description
Excel Agent is a "smart assistant who can understand Excel". It first breaks down the problem into steps, then executes and verifies the results step by step. It can understand user problems and uploaded file contents, propose feasible solutions, and select appropriate tools (system commands, generate and run Python code, network queries, etc.) to complete tasks.
Before using this, you need to make some configurations:
### Env
```
//requiredBasic LLM Model Configcurrently support Ark and OpenAI models.
// Ark model config
export ARK_API_KEY="" //requiredArk Model API Key
export ARK_MODEL="" //requiredArk Model name
export ARK_BASE_URL="" //optionalArk Model base_url
export ARK_REGION="" //optionalArk Model region
// OpenAI model config
export OPENAI_API_KEY="" // (requiredOpenAI Model API Key
export OPENAI_MODEL="" // (requiredOpenAI Model name
export OPENAI_BASE_URL="" // (optionalOpenAI base_url
export OPENAI_BY_AZURE="false" // (optionalOpenAI using Azure service or not
//optionalPython executable pathdefault using system python.
// It's recommended to use venv, and install pandas / numpy / matplotlib / openpyxl before lanunching this agent.
// When the code written by CodeAgent fails to run due to lack of dependencies, the pip command may be used to try to install dependencies.
// At this time, if you use python in the system environment, it may be blocked, causing the Excel Agent to fail to run and exit.
export EXCEL_AGENT_PYTHON_EXECUTABLE_PATH="python"
//optionalVision Model Configdefault null, which ImagepReader tool in ReportAgent will not activate.
export ARK_VISION_API_KEY="" // Ark Vision Model API Key
export ARK_VISION_MODEL="" // Ark Vision Model name
export ARK_VISION_BASE_URL="" // Ark Vision Model base_url
export ARK_VISION_REGION="" // Ark Vision Model region
```
### Input
The input for Excel Agent is a description of user requirements and a series of files to be processed:
- The first line in `main.go` represents the requirement description entered by the user:
```
func main() {
// query := schema.UserMessage("Count the recommended novel names and recommended times in the attachment file, and write the results to the file. The content with "" is the name of the novel and forms a table. The header is the name of the novel and the number of recommendations. The novels with the same name are listed in only one line, and the number of recommendations is added")
// query := schema.UserMessage("Read the content in simulated question. csv, put the question, answer, resolution and options in the same line in a standardized format, and simply write the answer to the resolution")
query := schema.UserMessage("Please help me extract the first column in question.csv table into a new csv")
}
```
- `adk/multiagent/deep/playground/input` is the default attachment input path. For example, the `question.csv` file mentioned in the above query needs to be placed in this directory before it can be read by the agent. In addition, it supports the configuration of the environment variable `EXCEL_AGENT_INPUT_DIR` to set the attachment input path (absolute path).
- Several sample files are provided in the path `adk/multiagent/deep/playground/test_data` for your test:
```
% tree adk/multiagent/deep/playground/test_data
adk/multiagent/deep/playground/test_data
├── questions.csv
├── 推荐小说.txt
└── 模拟出题.csv
1 directory, 3 files
```
### Output
The default working directory is `adk/multiagent/deep/playground/${uuid}`.
You can set your own working directory by setting env: `export EXCEL_AGENT_WORK_DIR="your_path""` (the absolute path before/$uuid).

@ -0,0 +1,112 @@
/*
* 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 agents
import (
"context"
"fmt"
"github.com/cloudwego/eino-ext/components/tool/commandline"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/prompt"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/params"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/tools"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/utils"
)
func NewCodeAgent(ctx context.Context, operator commandline.Operator) (adk.Agent, error) {
cm, err := utils.NewChatModel(ctx,
utils.WithMaxTokens(14125),
utils.WithTemperature(float32(1)),
utils.WithTopP(float32(1)),
)
if err != nil {
return nil, err
}
preprocess := []tools.ToolRequestPreprocess{tools.ToolRequestRepairJSON}
ca, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "CodeAgent",
Description: `This sub-agent is a code agent specialized in handling Excel files.
It receives a clear task and accomplish the task by generating Python code and execute it.
The agent leverages pandas for data analysis and manipulation, matplotlib for plotting and visualization, and openpyxl for reading and writing Excel files.
The React agent should invoke this sub-agent whenever stepwise Python coding for Excel file operations is required, ensuring precise and efficient task execution.`,
Instruction: `You are a code agent. Your workflow is as follows:
1. You will be given a clear task to handle Excel files.
2. You should analyse the task and use right tools to help coding.
3. You should write python code to finish the task.
4. You are preferred to write code execution result to another file for further usages.
You are in a react mode, and you should use the following libraries to help you finish the task:
- pandas: for data analysis and manipulation
- matplotlib: for plotting and visualization
- openpyxl: for reading and writing Excel files
Notice:
1. Tool Calls argument must be a valid json.
2. Tool Calls argument should do not contains invalid suffix like ']<|FunctionCallEnd|>'.
`,
Model: cm,
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{
tools.NewWrapTool(tools.NewBashTool(operator), preprocess, []tools.ToolResponsePostprocess{tools.FilePostProcess}),
tools.NewWrapTool(tools.NewTreeTool(operator), preprocess, nil),
tools.NewWrapTool(tools.NewEditFileTool(operator), preprocess, []tools.ToolResponsePostprocess{tools.EditFilePostProcess}),
tools.NewWrapTool(tools.NewReadFileTool(operator), preprocess, nil), // TODO: compress post process
tools.NewWrapTool(tools.NewPythonRunnerTool(operator), preprocess, []tools.ToolResponsePostprocess{tools.FilePostProcess}),
},
},
},
GenModelInput: func(ctx context.Context, instruction string, input *adk.AgentInput) ([]adk.Message, error) {
wd, ok := params.GetTypedContextParams[string](ctx, params.WorkDirSessionKey)
if !ok {
return nil, fmt.Errorf("work dir not found")
}
tpl := prompt.FromMessages(schema.Jinja2,
schema.SystemMessage(instruction),
schema.UserMessage(`WorkingDirectory: {{ working_dir }}
UserQuery: {{ user_query }}
CurrentTime: {{ current_time }}
`))
msgs, err := tpl.Format(ctx, map[string]any{
"working_dir": wd,
"user_query": utils.FormatInput(input.Messages),
"current_time": utils.GetCurrentTime(),
})
if err != nil {
return nil, err
}
return msgs, nil
},
OutputKey: "",
MaxIterations: 1000,
})
if err != nil {
return nil, err
}
return ca, nil
}

@ -0,0 +1,52 @@
/*
* 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 agents
import (
"context"
"github.com/cloudwego/eino-ext/components/tool/duckduckgo/v2"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/utils"
)
func NewWebSearchAgent(ctx context.Context) (adk.Agent, error) {
cm, err := utils.NewChatModel(ctx)
if err != nil {
return nil, err
}
searchTool, err := duckduckgo.NewTextSearchTool(ctx, &duckduckgo.Config{})
if err != nil {
return nil, err
}
return adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "WebSearchAgent",
Description: "WebSearchAgent utilizes the ReAct model to analyze input information and accomplish tasks using web search tools.",
Model: cm,
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{searchTool},
},
},
MaxIterations: 10,
})
}

@ -0,0 +1,218 @@
/*
* 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 generic
import (
"os"
"path/filepath"
"github.com/xuri/excelize/v2"
"golang.org/x/sync/errgroup"
)
type PreviewFile struct {
FilePath string `json:"file_path,omitempty" xml:"file_path"`
SingleFilePreviews []*SingleFilePreview `json:"single_file_previews,omitempty" xml:"single_file_previews>single_file_preview"`
}
type SingleFilePreview struct {
SheetName string `json:"sheet_name,omitempty" xml:"sheet_name"`
Header []*ExcelCell `json:"header" xml:"header"`
Content [][]*ExcelCell `json:"content,omitempty" xml:"content"`
MergedCells []*ExcelCell `json:"merged_cells,omitempty" xml:"merged_cells>merged_cell"`
}
type ExcelCell struct {
Address string `json:"address,omitempty" xml:"address"` // B1:D1 表示 B1 到 D1 范围的单元格, B1 表示单个单元格
Value string `json:"value,omitempty" xml:"value"` // 单元格的值
}
func PreviewPath(path string) ([]*PreviewFile, error) {
filePaths, err := getAllFiles(path)
if err != nil {
return nil, err
}
if len(filePaths) == 0 {
return nil, nil
}
resp := make([]*PreviewFile, len(filePaths))
eg := errgroup.Group{}
eg.SetLimit(10)
for i := range filePaths {
idx := i
fp := filePaths[idx]
eg.Go(func() error {
var (
pf *PreviewFile
e error
)
if ext := filepath.Ext(fp); ext != ".xlsx" { // .xls not support
pf = &PreviewFile{FilePath: fp}
} else {
pf, e = previewExcelDocument(path)
}
if e != nil {
return e
}
resp[idx] = pf
return nil
})
}
if err := eg.Wait(); err != nil {
return nil, err
}
return resp, nil
}
func getAllFiles(path string) ([]string, error) {
info, err := os.Stat(path)
if err != nil {
return nil, err
}
if !info.IsDir() {
if isHiddenFile(info.Name()) {
return nil, nil
}
return []string{path}, nil
}
var files []string
if err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if isHiddenFile(info.Name()) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
if !info.IsDir() {
files = append(files, path)
}
return nil
}); err != nil {
return nil, err
}
return files, nil
}
func previewExcelDocument(filePath string) (*PreviewFile, error) {
f, err := excelize.OpenFile(filePath)
if err != nil {
return nil, err
}
defer f.Close()
pf := &PreviewFile{
FilePath: filePath,
SingleFilePreviews: nil,
}
for _, sheetName := range f.GetSheetList() {
sfp, err := parseSheet(f, sheetName)
if err != nil {
return nil, err
}
pf.SingleFilePreviews = append(pf.SingleFilePreviews, sfp)
}
return pf, nil
}
func parseSheet(f *excelize.File, sheetName string) (*SingleFilePreview, error) {
preview := &SingleFilePreview{
SheetName: sheetName,
Header: make([]*ExcelCell, 0),
Content: make([][]*ExcelCell, 0),
MergedCells: make([]*ExcelCell, 0),
}
mcs := make([][][]int, 20)
mergedCells, err := f.GetMergeCells(sheetName)
if err != nil {
return nil, err
}
for _, cell := range mergedCells {
lcol, lrow, err := excelize.CellNameToCoordinates(cell.GetStartAxis())
if err != nil {
return nil, err
}
if lrow >= 20 {
continue
}
rcol, rrow, err := excelize.CellNameToCoordinates(cell.GetEndAxis())
if err != nil {
return nil, err
}
preview.MergedCells = append(preview.MergedCells, &ExcelCell{
Address: cell[0],
Value: cell.GetCellValue(),
})
r := mcs[lrow]
r = append(r, []int{lcol, lrow, rcol, rrow})
}
rowIter, err := f.Rows(sheetName)
if err != nil {
return nil, err
}
i := 0
for rowIter.Next() && i < 20 {
values, err := rowIter.Columns()
if err != nil {
return nil, err
}
var rowValues []*ExcelCell
for j, val := range values {
col, row := j+1, i+1
skip := false
if r := mcs[i]; len(r) > 0 {
for _, coverage := range r {
if col >= coverage[0] && row >= coverage[1] && col <= coverage[2] && row <= coverage[3] {
skip = true
break
}
}
}
if skip {
continue
}
addr, err := excelize.CoordinatesToCellName(col, row)
if err != nil {
return nil, err
}
rowValues = append(rowValues, &ExcelCell{Address: addr, Value: val})
}
if i == 0 {
preview.Header = rowValues
} else {
preview.Content = append(preview.Content, rowValues)
}
i++
}
return preview, nil
}
func isHiddenFile(name string) bool {
return len(name) > 0 && name[0] == '.'
}

@ -0,0 +1,86 @@
/*
* 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 generic
import (
"context"
"fmt"
"path/filepath"
"github.com/cloudwego/eino-ext/components/tool/commandline"
)
type FullPlan struct {
TaskID int `json:"task_id,omitempty"`
Status PlanStatus `json:"status,omitempty"`
AgentName string `json:"agent_name,omitempty"`
Desc string `json:"desc,omitempty"`
ExecResult *SubmitResult `json:"exec_result,omitempty"`
}
type PlanStatus string
const (
PlanStatusTodo PlanStatus = "todo"
PlanStatusDoing PlanStatus = "doing"
PlanStatusDone PlanStatus = "done"
PlanStatusFailed PlanStatus = "failed"
PlanStatusSkipped PlanStatus = "skipped"
)
var (
PlanStatusMapping = map[PlanStatus]string{
PlanStatusTodo: "待执行",
PlanStatusDoing: "执行中",
PlanStatusDone: "已完成",
PlanStatusFailed: "执行失败",
PlanStatusSkipped: "已跳过",
}
)
func (p *FullPlan) String() string {
status, ok := PlanStatusMapping[p.Status]
if !ok {
status = string(p.Status)
}
res := fmt.Sprintf("%d. **[%s]** %s", p.TaskID, status, p.Desc)
if p.ExecResult != nil {
res += fmt.Sprintf("\n%s", p.ExecResult.String())
}
return res
}
func (p *FullPlan) PlanString(n int) string {
if p.Status != PlanStatusDoing && p.Status != PlanStatusTodo {
return fmt.Sprintf("- [x] %d. %s", n, p.Desc)
}
return fmt.Sprintf("- [ ] %d. %s", n, p.Desc)
}
func FullPlan2String(plan []*FullPlan) string {
var planStr = "### 任务计划\n"
for i, p := range plan {
planStr += p.PlanString(i+1) + "\n"
}
return planStr
}
func Write2PlanMD(ctx context.Context, op commandline.Operator, wd string, plan []*FullPlan) error {
planStr := FullPlan2String(plan)
filePath := filepath.Join(wd, "plan.md")
return op.WriteFile(ctx, filePath, planStr)
}

@ -0,0 +1,81 @@
/*
* 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 generic
import (
"encoding/json"
"github.com/bytedance/sonic"
"github.com/cloudwego/eino/schema"
)
type Step struct {
Index int `json:"index"`
Desc string `json:"desc"`
}
type Plan struct {
Steps []Step `json:"steps"`
}
func (p *Plan) FirstStep() string {
if len(p.Steps) == 0 {
return ""
}
stepStr, _ := sonic.MarshalString(p.Steps[0])
return stepStr
}
func (p *Plan) MarshalJSON() ([]byte, error) {
type Alias Plan
return json.Marshal((*Alias)(p))
}
func (p *Plan) UnmarshalJSON(bytes []byte) error {
type Alias Plan
a := (*Alias)(p)
return json.Unmarshal(bytes, a)
}
var PlanToolInfo = &schema.ToolInfo{
Name: "create_plan",
Desc: "Generates a structured, step-by-step execution plan to solve a given complex task. Each step in the plan must be assigned to a specialized agent and must have a clear, actionable description.",
ParamsOneOf: schema.NewParamsOneOfByParams(
map[string]*schema.ParameterInfo{
"steps": {
Type: schema.Array,
ElemInfo: &schema.ParameterInfo{
Type: schema.Object,
SubParams: map[string]*schema.ParameterInfo{
"index": {
Type: schema.Integer,
Desc: "The sequential number of this step in the overall plan. **Must start from 1 and increment by exactly 1 for each subsequent step.**",
Required: true,
},
"desc": {
Type: schema.String,
Desc: "A clear, concise, and actionable description of the specific task to be performed in this step. It should be a direct instruction for the assigned agent.",
Required: true,
},
},
},
Desc: "different steps to follow, should be in sorted order",
Required: true,
},
},
),
}

@ -0,0 +1,81 @@
/*
* 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 generic
import (
"fmt"
"io/fs"
"path/filepath"
"strings"
)
type SubmitResult struct {
IsSuccess *bool `json:"is_success,omitempty"`
Result string `json:"result,omitempty"`
Files []*SubmitResultFile `json:"files,omitempty"`
}
type SubmitResultFile struct {
Path string `json:"path,omitempty"`
Desc string `json:"desc,omitempty"`
}
func (s *SubmitResult) String() string {
res := fmt.Sprintf("### 执行结果\n%s", s.Result)
if len(s.Files) > 0 {
res += "\n#### 中间产物"
}
for _, f := range s.Files {
res += fmt.Sprintf("\n- 描述:%s, 路径:%s", f.Desc, f.Path)
}
return res
}
func ListDir(dir string) ([]*SubmitResultFile, error) {
var resp []*SubmitResultFile
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if strings.HasPrefix(d.Name(), ".") {
return nil
}
if path == dir {
return nil
}
if d.IsDir() {
next := filepath.Join(dir, d.Name())
nextResp, err := ListDir(next)
if err != nil {
return err
}
resp = append(resp, nextResp...)
return nil
}
resp = append(resp, &SubmitResultFile{
Path: filepath.Join(filepath.Dir(dir), d.Name()),
})
return nil
})
if err != nil {
return nil, err
}
return resp, nil
}

@ -0,0 +1,194 @@
/*
* 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 main
import (
"context"
"log"
"os"
"path/filepath"
"time"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/adk/prebuilt/deep"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/google/uuid"
"github.com/cloudwego/eino-examples/adk/common/prints"
"github.com/cloudwego/eino-examples/adk/common/trace"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/agents"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/generic"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/params"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/tools"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/utils"
)
func main() {
// Set your own query here. e.g.
// query := schema.UserMessage("统计附件文件中推荐的小说名称及推荐次数,并将结果写到文件中。凡是带有《》内容都是小说名称,形成表格,表头为小说名称和推荐次数,同名小说只列一行,推荐次数相加")
// query := schema.UserMessage("Count the recommended novel names and recommended times in the attachment file, and write the results into the file. The content with "" is the name of the novel, forming a table. The header is the name of the novel and the number of recommendations. The novels with the same name are listed in one row, and the number of recommendations is added")
// query := schema.UserMessage("读取 模拟出题.csv 中的表格内容,规范格式将题目、答案、解析、选项放在同一行,简答题只把答案写入解析即可")
// query := schema.UserMessage("Read the table content in the 模拟出题.csv, put the question, answer, resolution and options in the same line in a standardized format, and simply write the answer into the resolution")
query := schema.UserMessage("请帮我将 questions.csv 表格中的第一列提取到一个新的 csv 中")
// query := schema.UserMessage("Please help me extract the first column in question.csv table into a new csv")
ctx := context.Background()
traceCloseFn, startSpanFn := trace.AppendCozeLoopCallbackIfConfigured(ctx)
defer traceCloseFn(ctx)
agent, err := newExcelAgent(ctx)
if err != nil {
log.Fatal(err)
}
// uuid as task id
id := uuid.New().String()
runner := adk.NewRunner(ctx, adk.RunnerConfig{
Agent: agent,
EnableStreaming: true,
})
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
var inputFileDir, workdir string
if env := os.Getenv("EXCEL_AGENT_INPUT_DIR"); env != "" {
inputFileDir = env
} else {
inputFileDir = filepath.Join(wd, "adk/multiagent/deep/playground/input")
}
if env := os.Getenv("EXCEL_AGENT_WORK_DIR"); env != "" {
workdir = filepath.Join(env, id)
} else {
workdir = filepath.Join(wd, "adk/multiagent/deep/playground", id)
}
if err = os.Mkdir(workdir, 0755); err != nil {
log.Fatal(err)
}
if err = os.CopyFS(workdir, os.DirFS(inputFileDir)); err != nil {
log.Fatal(err)
}
previews, err := generic.PreviewPath(workdir)
if err != nil {
log.Fatal(err)
}
ctx = params.InitContextParams(ctx)
params.AppendContextParams(ctx, map[string]interface{}{
params.FilePathSessionKey: inputFileDir,
params.WorkDirSessionKey: workdir,
params.UserAllPreviewFilesSessionKey: utils.ToJSONString(previews),
params.TaskIDKey: id,
})
ctx, endSpanFn := startSpanFn(ctx, "plan-execute-replan", query)
iter := runner.Run(ctx, []*schema.Message{query})
var (
lastMessage adk.Message
lastMessageStream *schema.StreamReader[adk.Message]
)
for {
event, ok := iter.Next()
if !ok {
break
}
if event.Output != nil && event.Output.MessageOutput != nil {
if lastMessageStream != nil {
lastMessageStream.Close()
}
if event.Output.MessageOutput.IsStreaming {
cpStream := event.Output.MessageOutput.MessageStream.Copy(2)
event.Output.MessageOutput.MessageStream = cpStream[0]
lastMessage = nil
lastMessageStream = cpStream[1]
} else {
lastMessage = event.Output.MessageOutput.Message
lastMessageStream = nil
}
}
prints.Event(event)
}
if lastMessage != nil {
endSpanFn(ctx, lastMessage)
} else if lastMessageStream != nil {
msg, _ := schema.ConcatMessageStream(lastMessageStream)
endSpanFn(ctx, msg)
} else {
endSpanFn(ctx, "finished without output message")
}
time.Sleep(time.Second * 30)
}
func newExcelAgent(ctx context.Context) (adk.Agent, error) {
operator := &LocalOperator{}
cm, err := utils.NewChatModel(ctx,
utils.WithMaxTokens(4096),
utils.WithTemperature(float32(0)),
utils.WithTopP(float32(0)),
)
if err != nil {
return nil, err
}
ca, err := agents.NewCodeAgent(ctx, operator)
if err != nil {
return nil, err
}
wa, err := agents.NewWebSearchAgent(ctx)
if err != nil {
return nil, err
}
deepAgent, err := deep.New(ctx, &deep.Config{
Name: "ExcelAgent",
Description: "an agent for excel task",
ChatModel: cm,
SubAgents: []adk.Agent{ca, wa},
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{
tools.NewWrapTool(tools.NewReadFileTool(operator), nil, nil),
tools.NewWrapTool(tools.NewTreeTool(operator), nil, nil),
},
},
},
MaxIteration: 100,
})
if err != nil {
return nil, err
}
return deepAgent, nil
}

@ -0,0 +1,78 @@
/*
* 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 main
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"runtime"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/params"
)
type LocalOperator struct{}
func (l *LocalOperator) ReadFile(ctx context.Context, path string) (string, error) {
b, err := os.ReadFile(path)
if err != nil {
return err.Error(), nil
}
return string(b), nil
}
func (l *LocalOperator) WriteFile(ctx context.Context, path string, content string) error {
return os.WriteFile(path, []byte(content), 0666)
}
func (l *LocalOperator) IsDirectory(ctx context.Context, path string) (bool, error) {
return true, nil
}
func (l *LocalOperator) Exists(ctx context.Context, path string) (bool, error) {
return true, nil
}
func (l *LocalOperator) RunCommand(ctx context.Context, command string) (string, error) {
wd, ok := params.GetTypedContextParams[string](ctx, params.WorkDirSessionKey)
if !ok {
return "", fmt.Errorf("work dir not found")
}
var shellCmd []string
switch runtime.GOOS {
case "windows":
shellCmd = []string{"cmd.exe", "/C", command}
default:
shellCmd = []string{"/bin/sh", "-c", command}
}
cmd := exec.CommandContext(ctx, shellCmd[0], shellCmd[1:]...)
cmd.Dir = wd
outBuf := new(bytes.Buffer)
errBuf := new(bytes.Buffer)
cmd.Stdout = outBuf
cmd.Stderr = errBuf
err := cmd.Run()
if err != nil {
return fmt.Sprintf("internal error:\ncommand: %v\n\nexec error: %v", cmd.String(), errBuf.String()), nil
}
return outBuf.String(), nil
}

@ -0,0 +1,24 @@
/*
* 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 params
const (
FilePathSessionKey = "file_path_session_key"
UserAllPreviewFilesSessionKey = "user_all_preview_files_session_key"
WorkDirSessionKey = "work_dir_session_key"
TaskIDKey = "task_id"
)

@ -0,0 +1,79 @@
/*
* 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 params
import (
"context"
"log"
"sync"
)
const customBizKey = "biz"
func InitContextParams(ctx context.Context) context.Context {
return context.WithValue(ctx, customBizKey, &sync.Map{}) // nolint
}
func AppendContextParams(ctx context.Context, values map[string]interface{}) {
params, ok := ctx.Value(customBizKey).(*sync.Map)
if !ok {
log.Printf("[params.AppendContextParams] Failed to get params from context")
return
}
for k, v := range values {
params.Store(k, v)
}
}
func GetTypedContextParams[T any](ctx context.Context, mapKey string) (T, bool) {
var empty T
value, ok := getContextParams(ctx, mapKey)
if !ok {
return empty, false
}
valueT, ok := value.(T)
if !ok {
return empty, false
}
return valueT, true
}
func MustGetContextParams[T any](ctx context.Context, mapKey string) T {
var empty T
value, ok := getContextParams(ctx, mapKey)
if !ok {
log.Printf("[params.MustGetContextParams] cannot get key: %v", mapKey)
return empty
}
valueT, ok := value.(T)
if !ok {
log.Printf("[params.MustGetContextParams] value not string, key: %v", mapKey)
return empty
}
return valueT
}
func getContextParams(ctx context.Context, mapKey string) (interface{}, bool) {
params, ok := ctx.Value(customBizKey).(*sync.Map)
if !ok {
log.Printf("[params.GetContextParams] Failed to get params from context")
return nil, false
}
return params.Load(mapKey)
}

@ -0,0 +1,25 @@
type,question,options,answer,explanation
multiple-choice, 计算机中存储数据的基本单位是?,A. 字节 (Byte); B. 位 (Bit); C. 千字节 (KB); D. 兆字节 (MB),B**,A. 位 (Bit)
multiple-choice, 操作系统的主要功能不包括?,A. 管理硬件资源B. 提供用户界面C. 编写程序代码D. 运行应用软件C**,A. 管理硬件资源
multiple-choice, 计算机网络中IP 地址用于?,A. 标识网络设备B. 加密数据传输C. 存储网页内容D. 连接外设A**,A. 标识网络设备
multiple-choice, 以下哪种存储设备是非易失性的?,A. RAM; B. 硬盘驱动器 (HDD); C. CPU 缓存D. 寄存器B**,A. RAM
multiple-choice, 编程语言中,循环结构的作用是?,A. 条件判断B. 重复执行代码C. 存储变量D. 输出结果B**,A. 条件判断
multiple-choice, 常见的计算机病毒传播方式不包括?,A. 电子邮件附件B. 网络下载C. 打印机传输D. 可移动存储设备C**,A. 电子邮件附件
multiple-choice, 计算机的中央处理器 (CPU) 主要负责?,A. 数据存储B. 指令执行C. 网络连接D. 显示图像B**,A. 数据存储
multiple-choice,HTML 用于?,A. 设计网页结构B. 写操作系统C. 开发数据库D. 处理图像A**,A. 设计网页结构
multiple-choice, 软件测试的主要目的是?,A. 编写代码B. 查找缺陷C. 设计界面D. 运行硬件B**,A. 编写代码
multiple-choice, 计算机中的 “云存储” 指的是?,A. 本地硬盘存储B. 网络远程存储C. USB 存储设备D. 光盘存储B**,A. 本地硬盘存储
multiple-choice, 二进制数 1010 转换为十进制是?,A. 10; B. 12; C. 15; D. 8,A**,A. 10
multiple-choice, 在网络协议中HTTP 用于?,A. 电子邮件传输B. 网页浏览C. 文件传输D. 数据加密B**,A. 电子邮件传输
multiple-choice, 计算机中的 RAM 是?,A. 只读存储器B. 随机存取存储器C. 永久存储D. 外部存储B**,A. 只读存储器
multiple-choice, 软件开发生命周期不包括?,A. 需求分析B. 设计C. 编译硬件D. 测试C**,A. 需求分析
multiple-choice, 防火墙的主要作用是?,A. 阻止病毒B. 监控网络流量C. 防止未授权访问D. 加快网络速度C**,A. 阻止病毒
multiple-choice, 计算机中的 “缓存” 主要用来?,A. 存储大量数据B. 提高数据访问速度C. 备份数据D. 连接外设B**,A. 存储大量数据
multiple-choice, 常见的编程语言不包括?,A. Python; B. Java; C. SQL; D. HTML,E**,A. Python
multiple-choice, 软件许可证的作用是?,A. 保护软件版权B. 增加软件功能C. 提高软件速度D. 备份软件A**,A. 保护软件版权
multiple-choice, 计算机病毒的防护措施包括?,A. 安装杀毒软件B. 开启防火墙C. 定期更新系统D. 以上全部D**,A. 安装杀毒软件
multiple-choice,ASCII 码用于?,A. 图像编码B. 文本编码C. 音频编码D. 视频编码B**,A. 图像编码
short-answer, 简述操作系统的主要功能。,,,答案:操作系统负责管理硬件资源,提供用户界面,协调软件运行,保障系统安全和稳定。
short-answer, 列举三种常见的存储设备。,,,答案:硬盘驱动器 (HDD)、固态硬盘 (SSD)、随机存取存储器 (RAM)。
short-answer, 解释什么是计算机网络的 IP 地址。,,,答案IP 地址是分配给网络中每个设备的唯一标识符,用于设备间通信。
short-answer, 描述软件开发生命周期的基本步骤。,,,答案:需求分析、设计、编码、测试、部署和维护。
1 type,question,options,answer,explanation
2 multiple-choice, 计算机中存储数据的基本单位是?,A. 字节 (Byte); B. 位 (Bit); C. 千字节 (KB); D. 兆字节 (MB),B**,A. 位 (Bit)
3 multiple-choice, 操作系统的主要功能不包括?,A. 管理硬件资源;B. 提供用户界面;C. 编写程序代码;D. 运行应用软件,C**,A. 管理硬件资源
4 multiple-choice, 计算机网络中,IP 地址用于?,A. 标识网络设备;B. 加密数据传输;C. 存储网页内容;D. 连接外设,A**,A. 标识网络设备
5 multiple-choice, 以下哪种存储设备是非易失性的?,A. RAM; B. 硬盘驱动器 (HDD); C. CPU 缓存;D. 寄存器,B**,A. RAM
6 multiple-choice, 编程语言中,循环结构的作用是?,A. 条件判断;B. 重复执行代码;C. 存储变量;D. 输出结果,B**,A. 条件判断
7 multiple-choice, 常见的计算机病毒传播方式不包括?,A. 电子邮件附件;B. 网络下载;C. 打印机传输;D. 可移动存储设备,C**,A. 电子邮件附件
8 multiple-choice, 计算机的中央处理器 (CPU) 主要负责?,A. 数据存储;B. 指令执行;C. 网络连接;D. 显示图像,B**,A. 数据存储
9 multiple-choice,HTML 用于?,A. 设计网页结构;B. 写操作系统;C. 开发数据库;D. 处理图像,A**,A. 设计网页结构
10 multiple-choice, 软件测试的主要目的是?,A. 编写代码;B. 查找缺陷;C. 设计界面;D. 运行硬件,B**,A. 编写代码
11 multiple-choice, 计算机中的 “云存储” 指的是?,A. 本地硬盘存储;B. 网络远程存储;C. USB 存储设备;D. 光盘存储,B**,A. 本地硬盘存储
12 multiple-choice, 二进制数 1010 转换为十进制是?,A. 10; B. 12; C. 15; D. 8,A**,A. 10
13 multiple-choice, 在网络协议中,HTTP 用于?,A. 电子邮件传输;B. 网页浏览;C. 文件传输;D. 数据加密,B**,A. 电子邮件传输
14 multiple-choice, 计算机中的 RAM 是?,A. 只读存储器;B. 随机存取存储器;C. 永久存储;D. 外部存储,B**,A. 只读存储器
15 multiple-choice, 软件开发生命周期不包括?,A. 需求分析;B. 设计;C. 编译硬件;D. 测试,C**,A. 需求分析
16 multiple-choice, 防火墙的主要作用是?,A. 阻止病毒;B. 监控网络流量;C. 防止未授权访问;D. 加快网络速度,C**,A. 阻止病毒
17 multiple-choice, 计算机中的 “缓存” 主要用来?,A. 存储大量数据;B. 提高数据访问速度;C. 备份数据;D. 连接外设,B**,A. 存储大量数据
18 multiple-choice, 常见的编程语言不包括?,A. Python; B. Java; C. SQL; D. HTML,E**,A. Python
19 multiple-choice, 软件许可证的作用是?,A. 保护软件版权;B. 增加软件功能;C. 提高软件速度;D. 备份软件,A**,A. 保护软件版权
20 multiple-choice, 计算机病毒的防护措施包括?,A. 安装杀毒软件;B. 开启防火墙;C. 定期更新系统;D. 以上全部,D**,A. 安装杀毒软件
21 multiple-choice,ASCII 码用于?,A. 图像编码;B. 文本编码;C. 音频编码;D. 视频编码,B**,A. 图像编码
22 short-answer, 简述操作系统的主要功能。,,,答案:操作系统负责管理硬件资源,提供用户界面,协调软件运行,保障系统安全和稳定。
23 short-answer, 列举三种常见的存储设备。,,,答案:硬盘驱动器 (HDD)、固态硬盘 (SSD)、随机存取存储器 (RAM)。
24 short-answer, 解释什么是计算机网络的 IP 地址。,,,答案:IP 地址是分配给网络中每个设备的唯一标识符,用于设备间通信。
25 short-answer, 描述软件开发生命周期的基本步骤。,,,答案:需求分析、设计、编码、测试、部署和维护。

@ -0,0 +1,114 @@
纯爱:强推系列】
【纯爱强推《星轨漫过掌心》by 林深时,天文观测者 X 高空滑翔伞运动员】
【纯爱强推《岁月棱镜》by 晚照,退役森林消防员 X 古典钢琴调律师】
【纯爱强推《次元边界线》by 顾昭昭,平行时空观测者 VS 物理竞赛魔王】
【纯爱强推《未寄的信笺》by 夏小满,投行合伙人 X 古籍修复师】
【纯爱强推《朝光里的星》by 周辞,旧书店少东家 X 话剧演员】
【纯爱强推《棋声落灯花》by 苏晚,围棋国手 X 人工智能算法工程师】
《砚边雪》by 柳月白 2、《旧巷人家》by 沈清川 3、《渡舟》by 楚怀衣 4、《朝暮》by 林回 5、《提灯照夜路》by 许深 6、《野风》by 陈默 7、《巷口烟火》by 温小酒 8、《云归处》by 叶知秋 9、《月诺》by 乔知年 10、《橘里》by 南枝
【双男主书名的 “隐藏密码”】
“当你看完这些文才会明白书名的真正含义。” 以下十本双男主小说书名的真实寓意!
1、两个同样骄傲的灵魂始终在等对方先说出那半句未讲完的话 ——《未完成的和弦》
2、我算遍了所有坐标系才发现你是我唯一解不开的变量 ——《第三象限》
3、他说要带我看黎明可我才发现他就是我黑暗里唯一的星 ——《星子坠落时》
4、那年巷口的梧桐树影藏着我六年没说出口的 “我等你”——《梧桐影里》
5、我以为跳下十七楼是结束没想到跌进的是他攒了整座城的温柔 ——《十七楼的风》
6、暗恋是坛埋了三年的酒我喝得酩酊大醉他却还在问 “这酒是什么味儿”——《春酿》
7、他说 “哥哥的糖罐儿永远有你一份”,于是我这辈子都没学会 “放手”——《糖罐儿》
8、我的爱像台风呼啸而过可站在风眼里的他却还在问 “今天天气好吗”——《风眼之内》
9、他总把话咽回喉间可我看得见他眼睛里藏着一整个未说出口的春天 ——《喉间痣》
10、我肚子上的疤是那年救他留下的可他不知道那也是我藏了十年的 “我喜欢你”——《旧疤》
【BL 养崽文 \ 崽崽爱好者狂喜!】
《云间小窝》by 青柠茶 2. 《星际幼崽守护计划》by 软糖派 3. 《捡来的小团子超黏人》by 糯米糍 4. 《全宇宙最萌幼崽》by 月亮糕 5. 《小仙童的凡间日常》by 桃花酿 6. 《宫墙里的小奶包》by 霜糖饼 7. 《第一次当爹有点慌》by 芋泥球 8. 《修真界幼崽生存指南》by 桂花糖 9. 《影帝的小宝贝超可爱》by 奶黄包 10. 《星际治愈幼崽手册》by 椰蓉卷 11. 《穿成豪门后爸的幸福生活》by 甜橙派 12. 《龙族幼崽的团宠日常》by 葡萄冻 13. 《大美人带崽闯江湖》by 草莓酱 14. 《带球跑后崽崽超黏人》by 芒果冰 15. 《娃综里的猫猫崽超会撩》by 樱桃蜜 16. 《夏日里的小奶音》by 西瓜霜 17. 《八零年代养崽记》by 绿豆糕 18. 《崽崽杂货店的小秘密》by 红豆沙 19. 《病美人带崽上娃综爆红啦》by 蜂蜜罐 20. 《O 装 B 后揣了总裁的崽》by 柠檬茶 21. 《变成哈士奇后被迫带崽》by 柴犬饼 22. 《暗河中的小灯盏》by 薄荷糖 23. 《穿成炮灰后揣了霸总的崽》by 蓝莓派 24. 《反派家里的小团宠》by 棉花糖 25. 《爸爸什么时候懂事呀》by 杏仁露 26. 《帝君失忆后忘了崽》by 桃花茶 27. 《反派亲爹的养崽日常》by 巧克力派 28. 《奉旨养胖小团子》by 曲奇饼 29. 《和理想型闪婚养崽的日子》by 奶茶冻 30. 《和渣攻离婚后揣了崽》by 奶盖茶 31. 《怀了帝君的崽后跑路》by 樱花酿 32. 《怀了校草的崽后怎么办》by 柚子茶 33. 《回档八零养崽记》by 桂圆糕
【万人迷・雄性修罗场爱好者狂喜!】
《月桂词》by 风过耳 2. 《反派小师叔有点甜》by 糖霜糕 3. 《漂亮小炮灰超吸粉》by 糯米团 4. 《绝版白月光的修罗场》by 桂花酿 5. 《万人迷的日常烦恼》by 杏仁糖 6. 《兼职爱豆的万人迷生活》by 奶黄包 7. 《黑月光的万人迷之路》by 巧克力派 8. 《攀折天之骄子的正确方式》by 草莓酱 9. 《饲养恶毒炮灰的小技巧》by 蓝莓派 10. 《漂亮玩家的通关指南》by 芒果冰 11. 《为什么反派都爱我》by 樱桃蜜 12. 《情敌每天都变美》by 柠檬茶 13. 《男主们的奇怪眼神》by 葡萄冻 14. 《师门的奇怪氛围》by 西瓜霜 15. 《漂亮活该当老婆》by 甜橙派 16. 《清纯 NPC 的修罗场》by 椰蓉卷 17. 《扮演主角后的麻烦》by 红豆沙 18. 《游戏里的仙界白月光》by 绿豆糕 19. 《订婚后他们都破防了》by 蜂蜜罐 20. 《漂亮小瞎子的万人迷之路》by 桃花酿 21. 《穿进万人迷文后人设崩了》by 芋泥球 22. 《ABO 世界的直男万人迷》by 薄荷糖 23. 《诡异世界的万人迷老实人》by 棉花糖 24. 《穿成万人迷的竹马后》by 杏仁露 25. 《愤怒值爆表的万人迷》by 曲奇饼 26. 《梦魇直播间的万人迷》by 奶茶冻 27. 《任务失败的万人迷》by 奶盖茶 28. 《不读圣贤书的万人迷》by 樱花酿 29. 《蛊惑万人迷的小技巧》by 柚子茶 30. 《Z 圈妲己的万人迷日常》by 桂圆糕 31. 《昭昭的万人迷之路》by 荔枝蜜 32. 《男友收割机的万人迷生活》by 火龙果 33. 《反派的盛世美颜我都有》by 菠萝蜜 34. 《心魔引的万人迷攻略》by 猕猴桃 35. 《点满美貌值后的万人迷》by 哈密瓜 36. 《为剑尊献上 BE 剧本》by 火龙果 37. 《二分之一教主的万人迷日常》by 菠萝派 38. 《万人嫌的有用一生》by 苹果派 39. 《贵族男校的万人迷》by 香蕉派 40. 《烈火熔金的万人迷之路》by 橙子派 41. 《劣云头的万人迷攻略》by 梨膏糖
【“成年人的浪漫” 双男主推荐】
推荐十本让人 “心跳加速” 的双男主小说,每本都有不一样的 “烟火气”!
生活里总有无尽的疲惫,双男主小说就是藏在琐碎里的糖 —— 比如《程序猿的秘密》by 东隅996 苦逼程序员 X 科技公司 CEO职场的压抑与茶水间的暧昧细节真实到像在看自己的故事《酒吧里的画家》by 晚山调酒师甩瓶子的弧度、画家醉眼朦胧的眼神写得像夏天的暴雨爽得人透心凉《医院里的温柔》by 素心医生深夜查房时与病患家属的对视克制的浪漫比直白的告白更戳心《信息素的秘密》by 故园ABO 设定里冷面 Alpha 与倔强 Omega 的 “互相驯服”,某平台评分 4.8,评论区全是 “嗑到昏迷”《废土上的救赎》by 风火末世里并肩打怪的两个幸存者篝火旁的对话比任何甜宠文都暖《悬疑中的爱情》by 可有可无,私家侦探与神秘客户的 “案中情”剧情像美剧一样紧凑感情线却甜到掉牙《极限运动的爱情》by 假恐龙,极限运动员与摄影师在悬崖边的互动,刺激得肾上腺素飙升……
【爹系攻・从小养到大的 “双向依赖”】
推荐十本 “把授宠成小尾巴” 的双男主文,恭是刻进骨子里的 “爹系”,哪怕自己吃糠咽菜,也要把最好的留给授:
1、《小不点儿》by 林远 2、《寻光者》by 慕云 3、《养父的小宝贝》by 沈清 4、《暗巷里的光》by 郑明 5、《落不下的糖》by 尤夏 6、《软乎乎的小受》by 雪碧 7、《星星的小秘密》by 三道 8、《蝶变的青春》by 麟川 9、《大哥的小尾巴》by 林深 10、《阴客的小宝贝》by 木晨
【权谋文 YYDS・精华汇总】
再也不怕文荒!收藏这 36 本慢慢读:
1《山河昭》2《盛世筹谋》3《风雪归》4《逐鹿记》5《乱世雄才》6《宫监志》7《天意昭》8《玉颜镇山河》9《当年封候路》10《天狼啸》11《山河约》12《权奸传》13《揽月记》14《君疾》15《帝王策》16《幸臣传》17《雨魄》18《权臣再世》19《天皇传》20《山河不夜》21《太傅传》22《权宦记》23《封疆记》24《温香记》25《山有木》26《居心》27《花近江》28《明月心》29《少年游》30《涉春冰》31《长安太平》32《督主记》33《离骚赋》34《江山许》35《金台记》36《暴君传》
【人生必看・酸涩文 “十课”】
入坑第一课《未说的话》被称为 “天花板”!你到哪一课了?
1、《未说的话》by 麦香 2、《楚天北》by 大风 3、《暗河的光》by 冷山 4、《风眼的光》by 潭月 5、《未寄的回信》by 清明 6、《落日余辉》by 稚晨 7、《纸鸢记》by 潭月 8、《苹果的秘密》by 孟云 9、《BE 救援系统》by 稚晨 10、《荒野的花》by 麦香
【“又爽又带感” 双男主推荐】
推荐十本 “剧情与感官双在线” 的双男主文,每本都是 “仙品”:
1、《易感期》by 东日 2、《社畜的爱情》by 东日 3、《遇灵》by 奶糖 4、《喻阳》by 夜吻 5、《秋鸣》by 茂林 6、《胜利》by 蛇影 7、《金牢》by 夜影 8、《太傅的日常》by 孟云 9、《反派的死亡计划》by 棺影 10、《浮沉》by 阳影
【“名字潦草但剧情真香” 双男主文】
这些文名字 “随便”,但剧情 “杀疯了”!你看过几本?
1、《一只小肥猪》by 金斗士 2、《太傅的麻烦事》by 孟云 3、《弃后遇发小》by 关山 4、《阴郁受重生》by 东施 5、《反派的死亡计划》by 棺影 6、《九零年代的大亨》by 骨刀 7、《Beta 重生记》by 白兔 8、《穿成竹马的万人迷》by 妾阳 9、《废土幼崽记》by 秃子 10、《垃圾桶里的男友》by 骑鲸
【“按喜好分类” 书单・总有一款适合你】
1、斧子《深情难觅》by 淮风、《人鱼的秘密》by 深海、《爱的坚持》by 诸事、《初冬的雪》by 夜影
2、谷科《放不下的人》by 尤夏、《昏灯里的家》by 麦香、《纸鸢记》by 潭月、《苹果的秘密》by 孟云
3、小 ma《封建往事》by 花卷、《夜色深处》by 淮风、《继父的爱》by 孟云、《遗产的秘密》by 锦鲤
4、小 san《干枯的地》by 风火、《三分之一的爱》by 半缘、《自由行的爱情》by 白滚、《凤凰茶》by 醉真
5、水仙《抱月记》by 归鸿、《恰好的时光》by 归鸿、《灵异演员的 APP》by 戏子、《感化失败后》by 妾阳
【“破碎感治愈” 双男主文】
爱看 “心理疾病受被治愈” 的看过来!破碎的灵魂终会相遇:
《无恙》by 北林、《落日》by 稚晨、《旧友新长》by 诗茶、《未得阳光》by 高台、《死里逃生》by 客云、《走进良夜》by 秦见、《心理罪》by 长云、《反差萌》by 匿名、《难言的痛》by 梨花、《炮灰的逆袭》by 困困、《夜的爱情》by 杳杳、《分手之后》by 萍仔、《沉入深海》by 布洛卡、《家宠》by 魏良、《气旋的爱》by 银壳、《暧昧限定》by 苏闲、《戒瘾》by 吃不饱、《夏日的客》by 小仲、《蛹的挣扎》by 阿苏、《零度的爱》by 何缺、《过分漂亮的他》by 一丛、《某一天》by 八千、《热汤软面包》by 姜可、《欺骗》by 冷山、《分手等待》by 无风、《暗里的情》by 三道
【“冷门但宝藏” 小说推荐】
这些文收藏量不低,剧情超有料!
1.《星际孤岛 [末世]》作者:灰雾(晋江 17W 收藏,种田 + 重生 + 末世 + 基建,指挥官攻 VS 医生受)
2.《银白纪元》作者:林远(晋江 20W 收藏,科幻 + 温馨 + 黑色幽默,杀手攻 VS 病弱受)
3.《沙漠求生记 [穿越]》作者:芃远(晋江 8K 收藏,穿越 + 种田 + 基建,无 CP
4.《扮演预言家成神 [无限]》作者:白桃(晋江 10W 收藏,无限流)
5.《咸鱼的叛逆人生》作者:一世(晋江 24W 收藏,系统 + 攻略 + 魔法世界)
6.《猎罪笔记 [刑侦]》作者:甲子(晋江 9K 收藏,悬疑 + 刑侦 + 快穿)
7.《坏种的逆袭》作者:大坑(晋江 1.9W 收藏,仙侠 + 龙傲天攻 VS 反派受)
8.《目标是嫁豪门总裁》作者常笑AO + 沙雕 + 万人迷 + 开挂)
9.《穿到民国做设计师》作者:西木(晋江 4.7W 收藏,民国 + 先婚后爱 + 经营)
10.《不装了,我是武神》作者:林风(晋江 5.7W 收藏,武神受 VS 总裁攻)
11.《朋友送的小鹦鹉超吵》作者:草莓(晋江 7.7W 收藏,萌宠 + 话痨受 VS 闷骚攻)
12.《系统,这助理非当不可?》作者:若风(晋江 6K 收藏,重生 + 系统 + 穿书 + AB 恋)
【“酷哥受” 系列・反差萌 yyds】
就爱 “拽哥内心软乎乎”!恭被迷得 “七荤八素” 的酷哥受文:
1、《刀去哪了》by 橙子 2、《秋鸣》by 茂林 3、《橙色的风》by 子木 4、《恶灵》by 杨风 5、《初三的六一》by 蛇蝎 6、《cos 的爱》by 图南 7、《学听话》by 幸云 8、《失忆遇男友》by 妾阳 9、《外套的秘密》by 不问 10、《风暴的爱》by 假龙
【虐渣攻・火葬场书单】
渣攻 “追妻火葬场” 太爽了!这 32 本让你 “解气到跺脚”:
《渣攻之子重生》by 司风 2. 《兔耳执事》by 麟川 3. 《刀去哪了》by 橙子 4. 《穿书之破絮》by 西瓜 5. 《风露的蜜》by 风露 6. 《乖一点》by 易云 7. 《快穿之垃圾桶男友》by 骑鲸 8. 《团子的爱》by 团子 9. 《鼎儿的痴情》by 鼎云 10. 《红蓝的崇关》by 生为 11. 《追妻火葬场》by 暧昧 12. 《鲜奶的春光》by 阿姆 13. 《蒸汽桃的复婚》by 蒸汽 14. 《暖灰的滚远》by 暖灰 15. 《诚心的非暴力》by 诚心 16. 《五军的分手》by 五军 17. 《图安安的万人迷》by 图安 18. 《东施的万人迷》by 东施 19. 《春雨的热泪》by 江上 20. 《千载的过期》by 千载 21. 《烤年糕的离婚》by 过年 22. 《三道的新朝》by 三道 23. 《blueky 的荒野》by blue 24. 《麦香的荒野》by 麦香 25. 《土申的蛇蝎》by 土申 26. 《眉如黛的剑》by 眉如 27. 《柳桐语的前夫》by 柳桐 28. 《橙子雨的日记》by 橙子 29. 《万尔的落星》by 万尔 30. 《一节藕的最爱》by 一节 31. 《驿使的别喜欢》by 驿使 32. 《风吹的翻身》by 风吹
【“直男攻自我 PUA” 系列】
一边嘴硬 “我不喜欢男人”,一边被老婆勾得 “神魂颠倒”!这些文太懂 “反差感”:
1、《针尖对麦芒》by 水千 2、《液态的欲》by 雨山 3、《抱我一下》by 咿云 4、《喜欢你的人设》by 稚晨 5、《别躲了》by 今云 6、《取消订单》by 图南 7、《矜持的表面》by 非期 8、《病历本》by 肉包 9、《云破》by 淮风 10、《社交的温度》by 卡丘
【“北林合集”・破镜重圆天花板】
北林的文 “写尽遗憾与重逢”,没看过的一定要补!
《玉珠》丁汉白 x 纪慎语 《心眼的秘密》梁承 x 乔苑林 《无恙》顾拙言 x 庄凡心 《偷风的秘密》沈若臻 x 项明章 《演员的跨界》瞿燕庭 x 陆文 《非常的关系》简辛 x 汪昊延 《安知的意》戚时安 x 沈多意 《斜阳的路》费原 x 路柯桐 《无嫌的两小》聂维山 x 尹千阳
【“黑暗致郁” 双男主文】
授 “身心俱疲”,恭 “偏执到疯狂”,结局 “要么救赎要么毁灭”:
1、《十大刑》by 小周 2、《逃的大师兄》by 万紫 3、《未明的天》by 老婆 4、《赛赶的交易》by 赛赶 5、《锈的小偷》by 一把 6、《盐的漂亮》by 给我 7、《盐的溺池》by 给我 8、《锈的凶神》by 一把 9、《鱼的透骨》by 鱼双 10、《万紫的腌臜》by 万紫
【“堪比《山河昭》” 的权谋文强推】
4 本 “剧情封神” 的权谋文,每本都 “烧脑又好嗑”:
1.《风雪归》by 弃吴
简介:清冷侯爷顾昭苦等竹马林深十年,却在酒醉后与酷似故人的将军沈川荒唐一夜。他将沈川当替身,沈川却因他吃醋发疯 —— 直到沈川记忆苏醒,揭晓自己就是 “战死” 的林深!
2.《君疾》by 如似
简介:楚昭为调查户部尚书李玄之死,散布 “爱慕苏慕” 的流言接近对方,意图探查苏党弱点。苏慕看穿其意图却未点破,两人在查案中数次遇险,暗生默契。
3.《摄政记》by 蝎子
简介:帝国濒临崩溃,幼帝登基,李昭从种小麦的齐王成为权倾朝野的摄政王,从田间地头直入权力中心,以铁腕与理想重塑山河,暗藏 “玄龙负日” 的救世预言。
4.《长安太平》by 盐
简介:先帝猝逝,幼帝登基,楚太后(垂帘派)与宁亲王李显(摄政派)对峙。苏昭以《党争论》殿试文章震动朝野,本欲中立,却因状元身份被迫卷入漩涡。

@ -0,0 +1,204 @@
### **一、选择题**(每题 2 分,共 40 分)
1. **计算机中存储数据的最小单位是?**
A. 字节(Byte)
B. 位(Bit)
C. 千字节(KB)
D. 兆字节(MB)
**答案B**
**解析**:位(Bit)是计算机中表示数据的最小单位,所有数据最终都转换为位来存储和处理。
2. **操作系统的主要功能不包括?**
A. 管理硬件资源
B. 提供用户界面
C. 编写程序代码
D. 运行应用软件
**答案C**
**解析**:操作系统负责管理硬件和软件资源,但不负责编写程序代码。
3. **IP地址的主要作用是**
A. 标识网络设备
B. 加密数据传输
C. 存储网页内容
D. 连接外设
**答案A**
**解析**IP地址是网络中设备的唯一标识用于设备间通信。
4. **以下哪种存储设备是非易失性的?**
A. RAM
B. 硬盘驱动器(HDD)
C. CPU缓存
D. 寄存器
**答案B**
**解析**:硬盘驱动器为非易失性存储,断电后数据仍然保留。
5. **循环结构在编程中的作用是?**
A. 条件判断
B. 重复执行代码
C. 存储变量
D. 输出结果
**答案B**
**解析**:循环结构用于重复执行一段代码直到满足条件。
6. **计算机病毒常见的传播方式不包括?**
A. 电子邮件附件
B. 网络下载
C. 打印机传输
D. 可移动存储设备
**答案C**
**解析**:打印机不用于传播病毒,病毒传播主要通过网络和存储设备。
7. **中央处理器(CPU)的主要职责是?**
A. 数据存储
B. 指令执行
C. 网络连接
D. 显示图像
**答案B**
**解析**CPU负责执行指令和处理数据是计算机的大脑。
8. **HTML语言用于**
A. 设计网页结构
B. 写操作系统
C. 开发数据库
D. 处理图像
**答案A**
**解析**HTML用来定义网页的结构和内容。
9. **软件测试的主要目的是?**
A. 编写代码
B. 查找缺陷
C. 设计界面
D. 运行硬件
**答案B**
**解析**:软件测试用于发现程序中的缺陷和错误。
10. **云存储的定义是?**
A. 本地硬盘存储
B. 网络远程存储
C. USB存储设备
D. 光盘存储
**答案B**
**解析**:云存储指通过网络将数据存储在远程服务器。
11. **二进制数1010转换为十进制是**
A. 10
B. 12
C. 15
D. 8
**答案A**
**解析**二进制1010等于十进制的10。
12. **HTTP协议用于**
A. 电子邮件传输
B. 网页浏览
C. 文件传输
D. 数据加密
**答案B**
**解析**HTTP协议是万维网数据传输的基础协议。
13. **RAM的特点是**
A. 只读存储器
B. 随机存取存储器
C. 永久存储
D. 外部存储
**答案B**
**解析**RAM是计算机的随机存取存储器断电数据丢失。
14. **软件开发生命周期不包含以下哪一项?**
A. 需求分析
B. 设计
C. 编译硬件
D. 测试
**答案C**
**解析**:编译硬件不是软件开发生命周期的一部分。
15. **防火墙的主要作用是?**
A. 阻止病毒
B. 监控网络流量
C. 防止未授权访问
D. 加快网络速度
**答案C**
**解析**:防火墙用于防止未授权访问网络。
16. **计算机缓存的主要功能是?**
A. 存储大量数据
B. 提高数据访问速度
C. 备份数据
D. 连接外设
**答案B**
**解析**:缓存用于临时存储数据,提高访问速度。
17. **以下哪项不是编程语言?**
A. Python
B. Java
C. SQL
D. HTML
**答案D**
**解析**HTML是标记语言不是编程语言。
18. **软件许可证的作用是?**
A. 保护软件版权
B. 增加软件功能
C. 提高软件速度
D. 备份软件
**答案A**
**解析**:许可证用来保护软件的版权和合法使用。
19. **防护计算机病毒的有效措施包括?**
A. 安装杀毒软件
B. 开启防火墙
C. 定期系统更新
D. 以上全部
**答案D**
**解析**:综合防护措施能更有效防止病毒感染。
20. **ASCII码用于**
A. 图像编码
B. 文本编码
C. 音频编码
D. 视频编码
**答案B**
**解析**ASCII码是文本字符的编码标准。
---
### **二、简答题**(每题 10 分,共 40 分)
1. **简述操作系统的主要功能。**
**答案**
- 管理硬件资源如CPU、内存、设备。
- 提供用户界面,方便用户操作。
- 管理文件系统,控制数据存储。
- 调度和管理进程,保障系统稳定运行。
**解析**:操作系统是计算机系统的核心软件,协调硬件与软件的工作。
2. **列举三种常见的存储设备并简述。**
**答案**
- 硬盘驱动器(HDD):非易失性大容量存储设备。
- 固态硬盘(SSD)高速非易失性存储性能优于HDD。
- 随机存取存储器(RAM):临时存储数据,速度快但断电数据丢失。
**解析**:不同存储设备各有特点,满足不同需求。
3. **什么是IP地址其作用是什么**
**答案**
IP地址是分配给网络中每个设备的唯一数字标识用于设备间定位和通信。
**解析**IP地址是实现网络互联的基础。
4. **简述软件开发生命周期的主要步骤。**
**答案**
- 需求分析:明确软件功能和需求。
- 设计:制定软件架构和详细设计。
- 编码:实现软件功能的程序编写。
- 测试:检测和修复软件缺陷。
- 部署与维护:发布软件并进行后续支持。
**解析**:生命周期管理确保软件质量和项目进度。
---
### **三、设计说明**
1. **选择题**
- 涵盖计算机基础知识各大模块,如存储单位、操作系统、网络协议、编程基础等。
- 设计了干扰项以测试对核心知识的理解,如“编译硬件”“打印机传输”等。
2. **简答题**
- 聚焦操作系统功能、存储设备、IP地址和软件开发流程考查理解与表达。
- 要求条理清晰、条款准确。
3. **解析定位**
- 解释与答案紧密结合,帮助理解知识点。
1 ### **一、选择题**(每题 2 分,共 40 分)
2 1. **计算机中存储数据的最小单位是?**
3 A. 字节(Byte)
4 B. 位(Bit)
5 C. 千字节(KB)
6 D. 兆字节(MB)
7 **答案:B**
8 **解析**:位(Bit)是计算机中表示数据的最小单位,所有数据最终都转换为位来存储和处理。
9 2. **操作系统的主要功能不包括?**
10 A. 管理硬件资源
11 B. 提供用户界面
12 C. 编写程序代码
13 D. 运行应用软件
14 **答案:C**
15 **解析**:操作系统负责管理硬件和软件资源,但不负责编写程序代码。
16 3. **IP地址的主要作用是?**
17 A. 标识网络设备
18 B. 加密数据传输
19 C. 存储网页内容
20 D. 连接外设
21 **答案:A**
22 **解析**:IP地址是网络中设备的唯一标识,用于设备间通信。
23 4. **以下哪种存储设备是非易失性的?**
24 A. RAM
25 B. 硬盘驱动器(HDD)
26 C. CPU缓存
27 D. 寄存器
28 **答案:B**
29 **解析**:硬盘驱动器为非易失性存储,断电后数据仍然保留。
30 5. **循环结构在编程中的作用是?**
31 A. 条件判断
32 B. 重复执行代码
33 C. 存储变量
34 D. 输出结果
35 **答案:B**
36 **解析**:循环结构用于重复执行一段代码直到满足条件。
37 6. **计算机病毒常见的传播方式不包括?**
38 A. 电子邮件附件
39 B. 网络下载
40 C. 打印机传输
41 D. 可移动存储设备
42 **答案:C**
43 **解析**:打印机不用于传播病毒,病毒传播主要通过网络和存储设备。
44 7. **中央处理器(CPU)的主要职责是?**
45 A. 数据存储
46 B. 指令执行
47 C. 网络连接
48 D. 显示图像
49 **答案:B**
50 **解析**:CPU负责执行指令和处理数据,是计算机的大脑。
51 8. **HTML语言用于?**
52 A. 设计网页结构
53 B. 写操作系统
54 C. 开发数据库
55 D. 处理图像
56 **答案:A**
57 **解析**:HTML用来定义网页的结构和内容。
58 9. **软件测试的主要目的是?**
59 A. 编写代码
60 B. 查找缺陷
61 C. 设计界面
62 D. 运行硬件
63 **答案:B**
64 **解析**:软件测试用于发现程序中的缺陷和错误。
65 10. **云存储的定义是?**
66 A. 本地硬盘存储
67 B. 网络远程存储
68 C. USB存储设备
69 D. 光盘存储
70 **答案:B**
71 **解析**:云存储指通过网络将数据存储在远程服务器。
72 11. **二进制数1010转换为十进制是?**
73 A. 10
74 B. 12
75 C. 15
76 D. 8
77 **答案:A**
78 **解析**:二进制1010等于十进制的10。
79 12. **HTTP协议用于?**
80 A. 电子邮件传输
81 B. 网页浏览
82 C. 文件传输
83 D. 数据加密
84 **答案:B**
85 **解析**:HTTP协议是万维网数据传输的基础协议。
86 13. **RAM的特点是?**
87 A. 只读存储器
88 B. 随机存取存储器
89 C. 永久存储
90 D. 外部存储
91 **答案:B**
92 **解析**:RAM是计算机的随机存取存储器,断电数据丢失。
93 14. **软件开发生命周期不包含以下哪一项?**
94 A. 需求分析
95 B. 设计
96 C. 编译硬件
97 D. 测试
98 **答案:C**
99 **解析**:编译硬件不是软件开发生命周期的一部分。
100 15. **防火墙的主要作用是?**
101 A. 阻止病毒
102 B. 监控网络流量
103 C. 防止未授权访问
104 D. 加快网络速度
105 **答案:C**
106 **解析**:防火墙用于防止未授权访问网络。
107 16. **计算机缓存的主要功能是?**
108 A. 存储大量数据
109 B. 提高数据访问速度
110 C. 备份数据
111 D. 连接外设
112 **答案:B**
113 **解析**:缓存用于临时存储数据,提高访问速度。
114 17. **以下哪项不是编程语言?**
115 A. Python
116 B. Java
117 C. SQL
118 D. HTML
119 **答案:D**
120 **解析**:HTML是标记语言,不是编程语言。
121 18. **软件许可证的作用是?**
122 A. 保护软件版权
123 B. 增加软件功能
124 C. 提高软件速度
125 D. 备份软件
126 **答案:A**
127 **解析**:许可证用来保护软件的版权和合法使用。
128 19. **防护计算机病毒的有效措施包括?**
129 A. 安装杀毒软件
130 B. 开启防火墙
131 C. 定期系统更新
132 D. 以上全部
133 **答案:D**
134 **解析**:综合防护措施能更有效防止病毒感染。
135 20. **ASCII码用于?**
136 A. 图像编码
137 B. 文本编码
138 C. 音频编码
139 D. 视频编码
140 **答案:B**
141 **解析**:ASCII码是文本字符的编码标准。
142 ---
143 ### **二、简答题**(每题 10 分,共 40 分)
144 1. **简述操作系统的主要功能。**
145 **答案**:
146 - 管理硬件资源,如CPU、内存、设备。
147 - 提供用户界面,方便用户操作。
148 - 管理文件系统,控制数据存储。
149 - 调度和管理进程,保障系统稳定运行。
150 **解析**:操作系统是计算机系统的核心软件,协调硬件与软件的工作。
151 2. **列举三种常见的存储设备并简述。**
152 **答案**:
153 - 硬盘驱动器(HDD):非易失性大容量存储设备。
154 - 固态硬盘(SSD):高速非易失性存储,性能优于HDD。
155 - 随机存取存储器(RAM):临时存储数据,速度快但断电数据丢失。
156 **解析**:不同存储设备各有特点,满足不同需求。
157 3. **什么是IP地址?其作用是什么?**
158 **答案**:
159 IP地址是分配给网络中每个设备的唯一数字标识,用于设备间定位和通信。
160 **解析**:IP地址是实现网络互联的基础。
161 4. **简述软件开发生命周期的主要步骤。**
162 **答案**:
163 - 需求分析:明确软件功能和需求。
164 - 设计:制定软件架构和详细设计。
165 - 编码:实现软件功能的程序编写。
166 - 测试:检测和修复软件缺陷。
167 - 部署与维护:发布软件并进行后续支持。
168 **解析**:生命周期管理确保软件质量和项目进度。
169 ---
170 ### **三、设计说明**
171 1. **选择题**:
172 - 涵盖计算机基础知识各大模块,如存储单位、操作系统、网络协议、编程基础等。
173 - 设计了干扰项以测试对核心知识的理解,如“编译硬件”“打印机传输”等。
174 2. **简答题**:
175 - 聚焦操作系统功能、存储设备、IP地址和软件开发流程,考查理解与表达。
176 - 要求条理清晰、条款准确。
177 3. **解析定位**:
178 - 解释与答案紧密结合,帮助理解知识点。

@ -0,0 +1,76 @@
/*
* 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 tools
import (
"context"
"encoding/json"
"github.com/cloudwego/eino-ext/components/tool/commandline"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
var (
bashToolInfo = &schema.ToolInfo{
Name: "bash",
Desc: `Run commands in a bash shell
* When invoking this tool, the contents of the \"command\" parameter does NOT need to be XML-escaped.
* You don't have access to the internet via this tool.
* You do have access to a mirror of common linux and python packages via apt and pip.
* State is persistent across command calls and discussions with the user.
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
* Please avoid commands that may produce a very large amount of output.
* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.`,
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"command": {
Type: "string",
Desc: "The command to execute",
Required: true,
},
}),
}
)
func NewBashTool(op commandline.Operator) tool.InvokableTool {
return &bashTool{op: op}
}
type bashTool struct {
op commandline.Operator
}
func (b *bashTool) Info(_ context.Context) (*schema.ToolInfo, error) {
return bashToolInfo, nil
}
type shellInput struct {
Command string `json:"command"`
}
func (b *bashTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
input := &shellInput{}
err := json.Unmarshal([]byte(argumentsInJSON), input)
if err != nil {
return "", err
}
if len(input.Command) == 0 {
return "command cannot be empty", nil
}
o := tool.GetImplSpecificOptions(&options{b.op}, opts...)
return o.op.RunCommand(ctx, input.Command)
}

@ -0,0 +1,85 @@
/*
* 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 tools
import (
"context"
"encoding/json"
"github.com/cloudwego/eino-ext/components/tool/commandline"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
var (
editFileToolInfo = &schema.ToolInfo{
Name: "edit_file",
Desc: `This is a tool for editing file, with parameters including the file path and the content to be edited.
During task processing, if there is a need to create a file or overwrite file content, this tool can be used.
Notice:
- If the file does not exist, this tool creates it with permissions perm (0666); otherwise it will truncates it before writing, without changing permissions.
- When using this tool, be sure that the file content is the complete full text; otherwise, it may cause loss or errors in the file content.
- Only supports writing to text file s; writing to xls/xlsx files is not supported.`,
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"path": {
Type: schema.String,
Desc: "file absolute path",
Required: true,
},
"content": {
Type: schema.String,
Desc: "file content",
Required: true,
},
}),
}
)
func NewEditFileTool(op commandline.Operator) tool.InvokableTool {
return &editFile{op: op}
}
type editFile struct {
op commandline.Operator
}
func (e *editFile) Info(ctx context.Context) (*schema.ToolInfo, error) {
return editFileToolInfo, nil
}
type editFileInput struct {
Path string `name:"path"`
Content string `name:"content"`
}
func (e *editFile) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
input := &editFileInput{}
err := json.Unmarshal([]byte(argumentsInJSON), input)
if err != nil {
return "", err
}
if len(input.Path) == 0 {
return "path can not be empty", nil
}
o := tool.GetImplSpecificOptions(&options{op: e.op}, opts...)
err = o.op.WriteFile(ctx, input.Path, input.Content)
if err != nil {
return err.Error(), nil
}
return "edit file success", nil
}

@ -0,0 +1,25 @@
/*
* 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 tools
import (
"github.com/cloudwego/eino-ext/components/tool/commandline"
)
type options struct {
op commandline.Operator
}

@ -0,0 +1,138 @@
/*
* 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 tools
import (
"context"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/cloudwego/eino-ext/components/tool/commandline"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
jsoniter "github.com/json-iterator/go"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/params"
)
var (
toolPythonRunnerInfo = &schema.ToolInfo{
Name: "python_runner",
Desc: `Write Python code to a file and execute it, and return the execution result.
Code would be overwritten to the same file when this tool is called multiple times.`,
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"code": {
Type: "string",
Desc: "Python code to be executed. " +
"The code MUST be enclosed in a Markdown code block starting with ```python and ending with ```. " +
"CRITICAL: The code within the block must be a raw, multi-line string with real newlines. " +
"It MUST NOT be a JSON-escaped string containing literal '\\n' or '\\\"' sequences. " +
"The code must be ready for direct execution without any unescaping. " +
"Do not generate code comments.",
Required: true,
},
}),
}
)
func NewPythonRunnerTool(op commandline.Operator) tool.InvokableTool {
return &pythonRunnerTool{op: op}
}
type pythonRunnerTool struct {
op commandline.Operator
}
func (p *pythonRunnerTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return toolPythonRunnerInfo, nil
}
func (p *pythonRunnerTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
code := tryExtractCodeSnippet(argumentsInJSON)
if code == "" {
return "", fmt.Errorf("python code is empty, original=%s", argumentsInJSON)
}
taskID := params.MustGetContextParams[string](ctx, params.TaskIDKey)
wd, ok := params.GetTypedContextParams[string](ctx, params.WorkDirSessionKey)
if !ok {
return "", fmt.Errorf("work dir not found")
}
filePath := filepath.Join(wd, fmt.Sprintf("%s.py", taskID))
if err := p.op.WriteFile(ctx, filePath, code); err != nil {
return fmt.Sprintf("failed to create python file %s: %v", filePath, err), nil
}
pyExecutablePath := os.Getenv("EXCEL_AGENT_PYTHON_EXECUTABLE_PATH")
if pyExecutablePath == "" {
pyExecutablePath = "python"
}
result, err := p.op.RunCommand(ctx, fmt.Sprintf("%s %s", pyExecutablePath, filePath))
if err != nil {
return "", fmt.Errorf("execute error: %w", err)
}
if strings.HasPrefix(result, "internal error") {
result = fmt.Sprintf("%s\n\n code after parse:\n%v", result, code)
}
return result, nil
}
func tryExtractCodeSnippet(res string) string {
var input struct {
Code string `json:"code"`
}
var codeToProcess string
err := jsoniter.Unmarshal([]byte(res), &input)
if err != nil {
codeToProcess = res
} else {
codeToProcess = input.Code
}
rawCode := extractCodeSnippet(codeToProcess)
unescapedCode := strings.NewReplacer(
"\\\\", "\\",
"\\n", "\n",
"\\r", "\r",
"\\t", "\t",
"\\\"", "\"",
"\\'", "'",
).Replace(rawCode)
return unescapedCode
}
func extractCodeSnippet(res string) string {
codePattern := regexp.MustCompile("(?s)```python\\s*(.*?)\\s*```")
codeMatch := codePattern.FindStringSubmatch(res)
if len(codeMatch) > 1 {
return strings.TrimSpace(codeMatch[1])
} else {
fallbackPattern := regexp.MustCompile("(?s)```\\s*(.*?)\\s*```")
fallbackMatch := fallbackPattern.FindStringSubmatch(res)
if len(fallbackMatch) > 1 {
return strings.TrimSpace(fallbackMatch[1])
} else {
return strings.TrimSpace(res)
}
}
}

@ -0,0 +1,94 @@
/*
* 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 tools
import (
"context"
"encoding/json"
"fmt"
"github.com/cloudwego/eino-ext/components/tool/commandline"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
var (
readFileToolInfo = &schema.ToolInfo{
Name: "read_file",
Desc: `This tool is used for reading file content, with parameters including the file path, starting line, and the number of lines to read. Content will be truncated if it is too long.
For xlsx files, each sheet's information will be returned sequentially upon a single call. If multiple sheets' information is needed, only one call is required. The call will return the headers, merged cell information, and the first n_rows of data for each sheet.`,
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"path": {
Type: schema.String,
Desc: "file absolute path",
Required: true,
},
"start_row": {
Type: schema.Integer,
Desc: "The starting line defaults to 1, meaning reading begins from the first line.",
},
"n_rows": {
Type: schema.Integer,
Desc: "Number of rows to read, -1 means reading from start_row to the end of the file, default is 20 rows. For xlsx, xls, and xlsm files, the default is 10 rows.",
},
}),
}
)
func NewReadFileTool(op commandline.Operator) tool.InvokableTool {
return &readFile{op: op}
}
type readFile struct {
op commandline.Operator
}
func (r *readFile) Info(ctx context.Context) (*schema.ToolInfo, error) {
return readFileToolInfo, nil
}
type readFileInput struct {
Path string `json:"path"`
StartRow int `json:"start_row"`
NRows int `json:"n_rows"`
}
func (r *readFile) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
input := &readFileInput{}
err := json.Unmarshal([]byte(argumentsInJSON), input)
if err != nil {
return "", err
}
if input.Path == "" {
return "path can not be empty", nil
}
if input.StartRow <= 0 {
input.StartRow = 1
}
if input.NRows <= 0 {
input.NRows = 20
}
o := tool.GetImplSpecificOptions(&options{op: r.op})
cmd := fmt.Sprintf("python3 -c \"import sys; lines = (line for idx, line in enumerate(open(sys.argv[1], encoding='utf-8')) if %d <= idx < %d); print(''.join(lines))\" %s",
input.StartRow, input.StartRow+input.NRows, input.Path)
content, err := o.op.RunCommand(ctx, cmd)
if err != nil {
return "", err
}
return content, nil
}

@ -0,0 +1,120 @@
/*
* 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 tools
import (
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"os"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
jsoniter "github.com/json-iterator/go"
)
var (
toolImageReaderInfo = &schema.ToolInfo{
Name: "image_reader",
Desc: "Tool for describing image content",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"query": {
Type: "string",
Desc: "Questions posed about the image",
Required: true,
},
"image_path": {
Type: "string",
Desc: "The path of the image file",
Required: true,
},
}),
}
)
func NewToolImageReader(visionModel model.BaseChatModel) tool.InvokableTool {
return &localToolImageReader{visionModel: visionModel}
}
type localToolImageReader struct {
visionModel model.BaseChatModel
}
func (t *localToolImageReader) Info(ctx context.Context) (*schema.ToolInfo, error) {
return toolImageReaderInfo, nil
}
func (t *localToolImageReader) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
var params struct {
Query string `json:"query"`
ImagePath string `json:"image_path"`
}
if err := jsoniter.Unmarshal([]byte(argumentsInJSON), &params); err != nil {
return "", err
}
if params.Query == "" || params.ImagePath == "" {
return "", errors.New("missing parameters")
}
f, err := os.Open(params.ImagePath)
if err != nil {
return fmt.Sprintf("open file error: %v, file path: %v", err, params.ImagePath), nil
}
defer f.Close()
fc, err := io.ReadAll(f)
if err != nil {
return fmt.Sprintf("read file error: %v, file path: %v", err, params.ImagePath), nil
}
mimeType := http.DetectContentType(fc)
b64 := base64.StdEncoding.EncodeToString(fc)
url := fmt.Sprintf("data:%s;base64,%s", mimeType, b64)
msgs := []*schema.Message{
schema.SystemMessage(""), // TODO: fill system prompt
schema.UserMessage(params.Query),
{
Role: schema.User,
UserInputMultiContent: []schema.MessageInputPart{
{
Type: schema.ChatMessagePartTypeImageURL,
Image: &schema.MessageInputImage{
MessagePartCommon: schema.MessagePartCommon{
URL: &url,
MIMEType: mimeType,
},
Detail: "",
},
},
},
},
}
resp, err := t.visionModel.Generate(ctx, msgs)
if err != nil {
return "", err
}
if resp.Content == "" {
return "", errors.New("response is empty")
}
return resp.Content, nil
}

@ -0,0 +1,127 @@
/*
* 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 tools
import (
"context"
"fmt"
"path/filepath"
"github.com/bytedance/sonic"
"github.com/cloudwego/eino-ext/components/tool/commandline"
"github.com/cloudwego/eino/adk/prebuilt/planexecute"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/generic"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/params"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/utils"
)
var (
submitResultToolInfo = &schema.ToolInfo{
Name: "submit_result",
Desc: "When all steps are completed without obvious problems, call this tool to end the task and report the final execution results to the user.",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"is_success": {
Type: schema.Boolean,
Desc: "success or nottrue/false",
},
"result": {
Type: schema.String,
Desc: "Task execution process and result",
},
"files": {
Type: schema.Array,
ElemInfo: &schema.ParameterInfo{
Desc: `The final file that needs to be delivered to the user (only the files that are successfully generated in the end are included, and Python scripts are not included by default unless explicitly requested by the user).
Select only the documents that can meet the original needs of users, and put the documents that best meet the needs to the first.
If there are many documents that meet the original needs of users, the report integrating these documents shall be delivered first, and the number of documents finally submitted shall be controlled within 3 as far as possible.`,
Type: schema.Object,
SubParams: map[string]*schema.ParameterInfo{
"path": {
Desc: "absolute path",
Type: schema.String,
},
"desc": {
Desc: "file content description",
Type: schema.String,
},
},
},
},
}),
}
SubmitResultReturnDirectly = map[string]bool{
"SubmitResult": true,
}
)
func NewToolSubmitResult(op commandline.Operator) tool.InvokableTool {
return &submitResultTool{op: op}
}
type submitResultTool struct {
op commandline.Operator
}
func (t *submitResultTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return submitResultToolInfo, nil
}
func (t *submitResultTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
args := &generic.SubmitResult{}
if err := sonic.Unmarshal([]byte(argumentsInJSON), args); err != nil {
return "", err
}
plan, _ := utils.GetSessionValue[*generic.Plan](ctx, planexecute.PlanSessionKey)
steps, _ := utils.GetSessionValue[[]planexecute.ExecutedStep](ctx, planexecute.ExecutedStepsSessionKey)
var fullPlan []*generic.FullPlan
for i, step := range steps {
fullPlan = append(fullPlan, &generic.FullPlan{
TaskID: i + 1,
Status: generic.PlanStatusDone,
Desc: step.Step,
ExecResult: &generic.SubmitResult{
IsSuccess: utils.PtrOf(true),
Result: step.Result,
},
})
}
for i := len(steps); i < len(plan.Steps); i++ {
step := plan.Steps[i]
fullPlan = append(fullPlan, &generic.FullPlan{
TaskID: len(fullPlan) + 1,
Status: generic.PlanStatusSkipped,
Desc: step.Desc,
})
}
wd, ok := params.GetTypedContextParams[string](ctx, params.WorkDirSessionKey)
if !ok {
return "", fmt.Errorf("work dir not found")
}
_ = t.op.WriteFile(ctx, filepath.Join(wd, "final_report.json"), argumentsInJSON)
_ = generic.Write2PlanMD(ctx, t.op, wd, fullPlan)
return utils.ToJSONString(&generic.FullPlan{AgentName: compose.END}), nil
}

@ -0,0 +1,71 @@
/*
* 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 tools
import (
"context"
"encoding/json"
"fmt"
"github.com/cloudwego/eino-ext/components/tool/commandline"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
var (
treeToolInfo = &schema.ToolInfo{
Name: "tree",
Desc: "This tool is used to view the directory tree structure; the parameter is the path to be viewed, and it returns the complete directory tree structure under that path.",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"path": {
Type: schema.String,
Desc: "absolute path",
Required: true,
},
}),
}
)
func NewTreeTool(op commandline.Operator) tool.InvokableTool {
return &tree{op: op}
}
type tree struct {
op commandline.Operator
}
func (t *tree) Info(ctx context.Context) (*schema.ToolInfo, error) {
return treeToolInfo, nil
}
type treeInput struct {
Path string `json:"path"`
}
func (t *tree) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
input := &treeInput{}
err := json.Unmarshal([]byte(argumentsInJSON), input)
if err != nil {
return "", err
}
if len(input.Path) == 0 {
return "path can not be empty", nil
}
o := tool.GetImplSpecificOptions(&options{t.op}, opts...)
return o.op.RunCommand(ctx, fmt.Sprintf("find %s", input.Path))
}

@ -0,0 +1,188 @@
/*
* 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 tools
import (
"context"
"encoding/json"
"fmt"
"log"
"path/filepath"
"strings"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
jsoniter "github.com/json-iterator/go"
"github.com/cloudwego/eino-examples/adk/multiagent/deep/utils"
)
type ToolRequestPreprocess func(ctx context.Context, baseTool tool.InvokableTool, toolArguments string) (string, error)
type ToolResponsePostprocess func(ctx context.Context, baseTool tool.InvokableTool, toolResponse, toolArguments string) (string, error)
func NewWrapTool(t tool.InvokableTool, preprocess []ToolRequestPreprocess, postprocess []ToolResponsePostprocess) tool.InvokableTool {
return &wrapTool{
baseTool: t,
preprocess: preprocess,
postprocess: postprocess,
}
}
type wrapTool struct {
baseTool tool.InvokableTool
preprocess []ToolRequestPreprocess
postprocess []ToolResponsePostprocess
}
func (w *wrapTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return w.baseTool.Info(ctx)
}
func (w *wrapTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
for _, pre := range w.preprocess {
var err error
argumentsInJSON, err = pre(ctx, w.baseTool, argumentsInJSON)
if err != nil {
log.Printf("[WrapTool.PreProcess] failed to process response: %v", err)
}
}
resp, err := w.baseTool.InvokableRun(ctx, argumentsInJSON, opts...)
if err != nil {
return "", err
}
for _, post := range w.postprocess {
resp, err = post(ctx, w.baseTool, resp, argumentsInJSON)
if err != nil {
log.Printf("[WrapTool.PostProcess] failed to process response: %v", err)
return resp, err
}
}
return resp, nil
}
func ToolRequestRepairJSON(ctx context.Context, baseTool tool.InvokableTool, toolArguments string) (string, error) {
return utils.RepairJSON(toolArguments), nil
}
type runResult struct {
Command string `json:"command"`
StdOut []*stdoutData `json:"stdout"`
StdErr []*stderrData `json:"stderr"`
FileChange []*fileChangeData `json:"file_change"`
ErrData []*errData `json:"err_data"`
}
type stdoutData struct {
Stdout string `json:"stdout"`
}
type stderrData struct {
Stderr string `json:"stderr"`
}
type errData struct {
Err string `json:"err"`
}
type fileChangeData struct {
FileType string `json:"file_type"`
Path string `json:"path"`
Type string `json:"type"`
Uri string `json:"uri"`
Url string `json:"url"`
MultiMediaInfo *multiMediaInfo `json:"multi_media_info,omitempty"`
}
type multiMediaInfo struct {
MediaType string `json:"media_type"`
AdditionalType string `json:"additional_type"`
AdditionalInfo string `json:"additional_info"`
}
func FilePostProcess(ctx context.Context, baseTool tool.InvokableTool, toolResponse, toolArguments string) (string, error) {
rawResult := runResult{}
if err := json.Unmarshal([]byte(toolResponse), &rawResult); err != nil {
return toolResponse, nil
}
type fileOutputFormat struct {
FileType string `json:"Change subject (file/directory)"`
Path string `json:"File/directory relative path"`
Type string `json:"Change type (create/delete/update)"`
}
var (
stdOut []string
fileChange []fileOutputFormat
stdErr []string
)
for _, item := range rawResult.StdOut {
if item != nil {
stdOut = append(stdOut, item.Stdout)
}
}
for _, item := range rawResult.FileChange {
if item != nil {
fileChange = append(fileChange, fileOutputFormat{
FileType: item.FileType,
Path: item.Path,
Type: item.Type,
})
}
}
for _, item := range rawResult.StdErr {
if item != nil {
stdErr = append(stdErr, item.Stderr)
}
}
for _, item := range rawResult.ErrData {
if item != nil {
stdErr = append(stdErr, item.Err)
}
}
var output string
if len(fileChange) > 0 {
fcText, _ := jsoniter.MarshalToString(fileChange)
output += "fileChange: \n" + fcText + "\n"
}
if len(stdErr) > 0 {
output += "shell command stderr and warnings:" + strings.Join(stdErr, "\n") + "\n"
}
if len(rawResult.StdOut) > 0 {
output += "shell command stdout: " + strings.Join(stdOut, "\n") + "\n"
}
return output, nil
}
func EditFilePostProcess(ctx context.Context, baseTool tool.InvokableTool, toolResponse, toolArguments string) (string, error) {
return fmt.Sprintf("Write file: %s success!", toolResponse), nil
}
func isImage(uri string) bool {
ext := filepath.Ext(uri)
for _, e := range []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".heic"} {
if ext == e {
return true
}
}
return false
}

@ -0,0 +1,132 @@
/*
* 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 utils
import (
"context"
"fmt"
"os"
"github.com/cloudwego/eino-ext/components/model/ark"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/components/model"
arkmodel "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
)
type CreateChatModelOption func(o *option)
func NewChatModel(ctx context.Context, opts ...CreateChatModelOption) (cm model.ToolCallingChatModel, err error) {
o := &option{}
for _, opt := range opts {
opt(o)
}
if modelName := os.Getenv("ARK_MODEL"); modelName != "" {
conf := &ark.ChatModelConfig{
APIKey: os.Getenv("ARK_API_KEY"),
BaseURL: os.Getenv("ARK_BASE_URL"),
Region: os.Getenv("ARK_REGION"),
Model: modelName,
MaxTokens: o.MaxTokens,
Temperature: o.Temperature,
TopP: o.TopP,
}
if o.DisableThinking != nil && *o.DisableThinking {
conf.Thinking = &arkmodel.Thinking{
Type: arkmodel.ThinkingTypeDisabled,
}
}
if o.JsonSchema != nil {
conf.ResponseFormat = &ark.ResponseFormat{
Type: arkmodel.ResponseFormatJSONSchema,
JSONSchema: &arkmodel.ResponseFormatJSONSchemaJSONSchemaParam{
Name: o.JsonSchema.Name,
Description: o.JsonSchema.Description,
Schema: o.JsonSchema.JSONSchema,
Strict: o.JsonSchema.Strict,
},
}
}
cm, err = ark.NewChatModel(ctx, conf)
} else if modelName = os.Getenv("OPENAI_MODEL"); modelName != "" {
conf := &openai.ChatModelConfig{
APIKey: os.Getenv("OPENAI_API_KEY"),
ByAzure: func() bool {
return os.Getenv("OPENAI_BY_AZURE") == "true"
}(),
BaseURL: os.Getenv("OPENAI_BASE_URL"),
Model: modelName,
MaxTokens: o.MaxTokens,
Temperature: o.Temperature,
TopP: o.TopP,
}
if o.JsonSchema != nil {
conf.ResponseFormat = &openai.ChatCompletionResponseFormat{
Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
JSONSchema: o.JsonSchema,
}
}
cm, err = openai.NewChatModel(ctx, conf)
}
if err != nil {
return nil, err
}
if cm == nil {
return nil, fmt.Errorf("no model config")
}
return cm, nil
}
type option struct {
MaxTokens *int
Temperature *float32
TopP *float32
DisableThinking *bool
JsonSchema *openai.ChatCompletionResponseFormatJSONSchema
}
func WithMaxTokens(maxTokens int) CreateChatModelOption {
return func(o *option) {
o.MaxTokens = &maxTokens
}
}
func WithTemperature(temp float32) CreateChatModelOption {
return func(o *option) {
o.Temperature = &temp
}
}
func WithTopP(topP float32) CreateChatModelOption {
return func(o *option) {
o.TopP = &topP
}
}
func WithDisableThinking(disable bool) CreateChatModelOption {
return func(o *option) {
o.DisableThinking = &disable
}
}
func WithResponseFormatJsonSchema(schema *openai.ChatCompletionResponseFormatJSONSchema) CreateChatModelOption {
return func(o *option) {
o.JsonSchema = schema
}
}

@ -0,0 +1,116 @@
/*
* 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 utils
import (
"context"
"fmt"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/adk/prebuilt/planexecute"
"github.com/kaptinlin/jsonrepair"
)
type panicErr struct {
info any
stack []byte
}
func (p *panicErr) Error() string {
return fmt.Sprintf("panic error: %v, \nstack: %s", p.info, string(p.stack))
}
// NewPanicErr creates a new panic error.
// panicErr is a wrapper of panic info and stack trace.
// it implements the error interface, can print error message of info and stack trace.
func NewPanicErr(info any, stack []byte) error {
return &panicErr{
info: info,
stack: stack,
}
}
func PtrOf[T any](v T) *T {
return &v
}
func FormatInput(input []adk.Message) string {
var sb strings.Builder
for _, msg := range input {
sb.WriteString(msg.Content)
sb.WriteString("\n")
}
return sb.String()
}
type TaskGroup interface {
Go(f func() error)
Wait() error
}
func ToJSONString(v interface{}) string {
str, _ := sonic.MarshalString(v)
return str
}
func GetCurrentTime() string {
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
// 出现错误时 fallback 到本地时间
return time.Now().Format("2006-01-02 15:04:05 MST")
}
return time.Now().In(loc).Format("2006-01-02 15:04:05 MST")
}
func RepairJSON(input string) string {
input = strings.TrimPrefix(input, "<|FunctionCallBegin|>")
input = strings.TrimSuffix(input, "<|FunctionCallEnd|>")
input = strings.TrimPrefix(input, "<think>")
output, err := jsonrepair.JSONRepair(input)
if err != nil {
return input
}
return output
}
func GetSessionValue[T any](ctx context.Context, key string) (T, bool) {
v, ok := adk.GetSessionValue(ctx, key)
if !ok {
var zero T
return zero, false
}
t, ok := v.(T)
if !ok {
var zero T
return zero, false
}
return t, true
}
func FormatExecutedSteps(in []planexecute.ExecutedStep) string {
var sb strings.Builder
for idx, m := range in {
sb.WriteString(fmt.Sprintf("## %d. Step: %v\n Result: %v\n\n", idx+1, m.Step, m.Result))
}
return sb.String()
}

@ -47,7 +47,7 @@ func main() {
// query := schema.UserMessage("读取 模拟出题.csv 中的表格内容,规范格式将题目、答案、解析、选项放在同一行,简答题只把答案写入解析即可")
// query := schema.UserMessage("Read the table content in the 模拟出题.csv, put the question, answer, resolution and options in the same line in a standardized format, and simply write the answer into the resolution")
query := schema.UserMessage("请帮我将 question.csv 表格中的第一列提取到一个新的 csv 中")
query := schema.UserMessage("请帮我将 questions.csv 表格中的第一列提取到一个新的 csv 中")
// query := schema.UserMessage("Please help me extract the first column in question.csv table into a new csv")
ctx := context.Background()

@ -6,7 +6,7 @@ toolchain go1.24.9
require (
github.com/bytedance/sonic v1.14.2
github.com/cloudwego/eino v0.5.10
github.com/cloudwego/eino v0.5.14
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.4
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20250117061805-cd80d1780d76
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20250117061805-cd80d1780d76

@ -113,8 +113,8 @@ 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.10 h1:/U5el2Ky5nKY1mE3nKHh8fnPKka5xlC19PW0s/jvbxw=
github.com/cloudwego/eino v0.5.10/go.mod h1:N6E+toMzWw/3ql0IVM5n5lbYFCeblCYx7ebH16kt1JQ=
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-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/components/document/parser/html v0.0.0-20250117061805-cd80d1780d76 h1:kK4f2kunb5xlc0XTkg6wkjy8Z/BDfJjWAVm9EOdRErg=

Loading…
Cancel
Save