feat(adk): add search tool middleware example (#167) (#180)

drew/english
Megumin 2 months ago committed by GitHub
parent cdc720df8c
commit 6114f39861
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,135 @@
/*
* 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"
"fmt"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/components/tool/utils"
)
type GetWeatherInput struct {
Location string `json:"location" jsonschema:"description=The city and state, e.g. San Francisco, CA"`
Unit string `json:"unit,omitempty" jsonschema:"enum=celsius,enum=fahrenheit,description=The unit of temperature"`
}
type GetWeatherOutput struct {
Temperature float64 `json:"temperature"`
Unit string `json:"unit"`
Condition string `json:"condition"`
}
type GetForecastInput struct {
Location string `json:"location" jsonschema:"description=The city and state"`
Days int `json:"days" jsonschema:"description=Number of days to forecast (1-10)"`
}
type GetForecastOutput struct {
Forecasts []DayForecast `json:"forecasts"`
}
type DayForecast struct {
Day string `json:"day"`
Temperature float64 `json:"temperature"`
Condition string `json:"condition"`
}
type GetStockPriceInput struct {
Ticker string `json:"ticker" jsonschema:"description=Stock ticker symbol (e.g., AAPL, GOOGL)"`
IncludeHistory bool `json:"include_history,omitempty" jsonschema:"description=Include historical data"`
}
type GetStockPriceOutput struct {
Ticker string `json:"ticker"`
Price float64 `json:"price"`
Change float64 `json:"change"`
}
type ConvertCurrencyInput struct {
Amount float64 `json:"amount" jsonschema:"description=Amount to convert"`
FromCurrency string `json:"from_currency" jsonschema:"description=Source currency code (e.g., USD)"`
ToCurrency string `json:"to_currency" jsonschema:"description=Target currency code (e.g., EUR)"`
}
type ConvertCurrencyOutput struct {
OriginalAmount float64 `json:"original_amount"`
ConvertedAmount float64 `json:"converted_amount"`
ExchangeRate float64 `json:"exchange_rate"`
}
func createWeatherTools() []tool.BaseTool {
getWeather, _ := utils.InferTool(
"get_weather",
"Get the current weather in a given location",
func(ctx context.Context, input *GetWeatherInput) (*GetWeatherOutput, error) {
return &GetWeatherOutput{
Temperature: 22.5,
Unit: input.Unit,
Condition: "Sunny",
}, nil
},
)
getForecast, _ := utils.InferTool(
"get_forecast",
"Get the weather forecast for multiple days ahead",
func(ctx context.Context, input *GetForecastInput) (*GetForecastOutput, error) {
forecasts := make([]DayForecast, input.Days)
for i := 0; i < input.Days; i++ {
forecasts[i] = DayForecast{
Day: fmt.Sprintf("Day %d", i+1),
Temperature: 20.0 + float64(i),
Condition: "Partly Cloudy",
}
}
return &GetForecastOutput{Forecasts: forecasts}, nil
},
)
return []tool.BaseTool{getWeather, getForecast}
}
func createFinanceTools() []tool.BaseTool {
getStockPrice, _ := utils.InferTool(
"get_stock_price",
"Get the current stock price and market data for a given ticker symbol",
func(ctx context.Context, input *GetStockPriceInput) (*GetStockPriceOutput, error) {
return &GetStockPriceOutput{
Ticker: input.Ticker,
Price: 150.25,
Change: 2.5,
}, nil
},
)
convertCurrency, _ := utils.InferTool(
"convert_currency",
"Convert an amount from one currency to another using current exchange rates",
func(ctx context.Context, input *ConvertCurrencyInput) (*ConvertCurrencyOutput, error) {
rate := 0.85
return &ConvertCurrencyOutput{
OriginalAmount: input.Amount,
ConvertedAmount: input.Amount * rate,
ExchangeRate: rate,
}, nil
},
)
return []tool.BaseTool{getStockPrice, convertCurrency}
}

@ -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 main
import (
"context"
"fmt"
"log"
"os"
"github.com/cloudwego/eino-ext/components/model/ark"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/adk/middlewares/dynamictool/toolsearch"
"github.com/cloudwego/eino-examples/adk/common/prints"
)
func main() {
ctx := context.Background()
weatherTools := createWeatherTools()
financeTools := createFinanceTools()
allDynamicTools := append(weatherTools, financeTools...)
toolSearchMiddleware, err := toolsearch.New(ctx, &toolsearch.Config{
DynamicTools: allDynamicTools,
})
if err != nil {
fmt.Printf("failed to create tool search middleware: %v\n", err)
return
}
chatModel, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
APIKey: os.Getenv("ARK_API_KEY"),
Model: os.Getenv("ARK_MODEL"),
})
if err != nil {
log.Fatal(err)
}
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "tool_search_agent",
Description: "An agent that can dynamically search and use tools from a large tool library",
Instruction: `You are a helpful assistant.`,
Model: chatModel,
Handlers: []adk.ChatModelAgentMiddleware{
toolSearchMiddleware,
},
})
if err != nil {
fmt.Printf("failed to create agent: %v\n", err)
return
}
runner := adk.NewRunner(ctx, adk.RunnerConfig{
Agent: agent,
EnableStreaming: true,
})
iter := runner.Query(ctx, "What's the weather in Beijing?")
for {
event, ok := iter.Next()
if !ok {
break
}
prints.Event(event)
}
}

@ -0,0 +1,105 @@
/*
* 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"
"fmt"
"log"
"os"
"path/filepath"
"github.com/cloudwego/eino-ext/adk/backend/local"
"github.com/cloudwego/eino-ext/components/model/ark"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/adk/middlewares/filesystem"
"github.com/cloudwego/eino/adk/middlewares/skill"
"github.com/cloudwego/eino-examples/adk/common/prints"
)
func main() {
ctx := context.Background()
pwd, _ := os.Getwd()
workDir := filepath.Join(pwd, "adk", "middlewares", "skill", "workdir")
skillsDir := filepath.Join(workDir, "skills")
cm, err := ark.NewChatModel(ctx, &ark.ChatModelConfig{
APIKey: os.Getenv("ARK_API_KEY"),
Model: os.Getenv("ARK_MODEL"),
})
if err != nil {
log.Fatal(err)
}
be, err := local.NewBackend(ctx, &local.Config{})
if err != nil {
log.Fatal(err)
}
fsm, err := filesystem.New(ctx, &filesystem.MiddlewareConfig{
Backend: be,
})
if err != nil {
log.Fatal(err)
}
skillBackend, err := skill.NewBackendFromFilesystem(ctx, &skill.BackendFromFilesystemConfig{
Backend: be,
BaseDir: skillsDir,
})
if err != nil {
log.Fatalf("Failed to create skill backend: %v", err)
}
sm, err := skill.NewMiddleware(ctx, &skill.Config{
Backend: skillBackend,
})
if err != nil {
log.Fatalf("Failed to create skill middleware: %v", err)
}
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "LogAnalysisAgent",
Description: "An agent that can analyze logs",
Instruction: "You are a helpful assistant.",
Model: cm,
Handlers: []adk.ChatModelAgentMiddleware{fsm, sm},
})
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
runner := adk.NewRunner(ctx, adk.RunnerConfig{
Agent: agent,
})
input := fmt.Sprintf("Analyze the %s file", filepath.Join(workDir, "test.log"))
log.Println("User: ", input)
iterator := runner.Query(ctx, input)
for {
event, ok := iterator.Next()
if !ok {
break
}
if event.Err != nil {
log.Printf("Error: %v\n", event.Err)
break
}
prints.Event(event)
}
}

@ -0,0 +1,24 @@
---
name: log_analyzer
description: A skill to analyze log files for errors and warnings using a Python script.
---
# Log Analyzer Skill
This skill analyzes a log file to count the occurrences of "ERROR" and "WARNING" and lists the lines where they appear.
## Capability
The skill provides a Python script named `analyze.py` located in this directory. You can use this script to analyze any text file.
## Usage
To use this skill, execute the `analyze.py` script with the target log file as an argument.
### Example
```bash
python3 {{.BaseDirectory}}/scripts/analyze.py /path/to/logfile.log
```
**Note**: Replace `/path/to/logfile.log` with the actual path of the file you want to analyze.

@ -0,0 +1,46 @@
import sys
import os
def analyze_log(file_path):
if not os.path.exists(file_path):
print(f"Error: File '{file_path}' not found.")
return
error_count = 0
warning_count = 0
error_lines = []
warning_lines = []
try:
with open(file_path, 'r') as f:
for i, line in enumerate(f, 1):
content = line.strip()
if "ERROR" in content:
error_count += 1
error_lines.append(f"Line {i}: {content}")
elif "WARNING" in content:
warning_count += 1
warning_lines.append(f"Line {i}: {content}")
print(f"Analysis Result for {file_path}:")
print(f"Total Errors: {error_count}")
print(f"Total Warnings: {warning_count}")
if error_count > 0:
print("\nError Details:")
for line in error_lines:
print(line)
if warning_count > 0:
print("\nWarning Details:")
for line in warning_lines:
print(line)
except Exception as e:
print(f"An error occurred while reading the file: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 analyze.py <log_file_path>")
else:
analyze_log(sys.argv[1])

@ -0,0 +1,6 @@
[2024-05-20 10:00:00] INFO: Service started.
[2024-05-20 10:01:23] WARNING: High memory usage detected.
[2024-05-20 10:02:15] ERROR: Database connection failed.
[2024-05-20 10:03:00] INFO: Retry connection.
[2024-05-20 10:03:05] ERROR: Connection timed out.
[2024-05-20 10:05:00] INFO: Service stopped.

@ -6,7 +6,8 @@ require (
github.com/alicebob/miniredis/v2 v2.35.0 github.com/alicebob/miniredis/v2 v2.35.0
github.com/bytedance/sonic v1.15.0 github.com/bytedance/sonic v1.15.0
github.com/chromedp/chromedp v0.9.5 github.com/chromedp/chromedp v0.9.5
github.com/cloudwego/eino v0.7.37 github.com/cloudwego/eino v0.8.0
github.com/cloudwego/eino-ext/adk/backend/local v0.2.1
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.8 github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.8
github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20251117090452-bd6375a0b3cf github.com/cloudwego/eino-ext/components/document/parser/html v0.0.0-20251117090452-bd6375a0b3cf
github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20251117090452-bd6375a0b3cf github.com/cloudwego/eino-ext/components/document/parser/pdf v0.0.0-20251117090452-bd6375a0b3cf
@ -41,6 +42,7 @@ require (
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bluele/gcache v0.0.2 // indirect github.com/bluele/gcache v0.0.2 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect

@ -83,6 +83,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
@ -127,8 +129,10 @@ 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/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 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/eino v0.7.37 h1:T73Y/8X7ERW4h3jP+brB/I4+N5ATDyGLx5bs2H4ev8I= github.com/cloudwego/eino v0.8.0 h1:DLbrgEAloA+l7aR2qim7qQocQB48DjPrb8LzG3PYMHY=
github.com/cloudwego/eino v0.7.37/go.mod h1:nA8Vacmuqv3pqKBQbTWENBLQ8MmGmPt/WqiyLeB8ohQ= github.com/cloudwego/eino v0.8.0/go.mod h1:+2N4nsMPxA6kGBHpH+75JuTfEcGprAMTdsZESrShKpU=
github.com/cloudwego/eino-ext/adk/backend/local v0.2.1 h1:sZ4f21SFzygzVXI6ppkkZom6JOibAjvS+YT2GZMqIy0=
github.com/cloudwego/eino-ext/adk/backend/local v0.2.1/go.mod h1:os5Tq5FuSoz/MLqAdZER3ip49Oef9prc0kVsKsPYO48=
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.8 h1:lqOAH/EWWRJuv9awmMxETEfcer2Gq2AVuZrrKqP+CzA= github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.8 h1:lqOAH/EWWRJuv9awmMxETEfcer2Gq2AVuZrrKqP+CzA=
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.8/go.mod h1:1FgtKIRv/LrUVuA7ojeViyRQhuIKaUDQOf+KjHuW+cg= github.com/cloudwego/eino-ext/callbacks/cozeloop v0.1.8/go.mod h1:1FgtKIRv/LrUVuA7ojeViyRQhuIKaUDQOf+KjHuW+cg=
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 h1:Uwh3VT+xPrfDjM677dj1pSidCzBFoTrYlC274kEci5w=

Loading…
Cancel
Save