Ajout de SEARXNG pour les recherches web
This commit is contained in:
1 parent
87eeb41364
commit
345129c7bf
30 files changed
+919
-166
No files matched your search
@@ -15,6 +15,7 @@ type Database interface {
|
||||
ProviderRepository() domain.ProviderRepository
|
||||
AgentRepository() domain.AgentRepository
|
||||
ChatRepository() domain.ChatRepository
|
||||
ToolRepository() domain.ToolRepository
|
||||
Migrate() error
|
||||
Close()
|
||||
}
|
||||
|
||||
@@ -79,3 +79,7 @@ func (db *TursoDB) AgentRepository() domain.AgentRepository {
|
||||
func (db *TursoDB) ChatRepository() domain.ChatRepository {
|
||||
return NewTursoChatRepository(db.DB)
|
||||
}
|
||||
|
||||
func (db *TursoDB) ToolRepository() domain.ToolRepository {
|
||||
return NewTursoToolRepository(db.DB)
|
||||
}
|
||||
@@ -95,9 +95,8 @@ create table tools (
|
||||
id text not null primary key,
|
||||
team_id text not null,
|
||||
name text not null,
|
||||
type text not null,
|
||||
enabled numeric not null default 1,
|
||||
configuration text not null default '{}',
|
||||
configuration text not null default '',
|
||||
_date_created numeric not null default current_timestamp,
|
||||
_date_updated numeric,
|
||||
_version text not null
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package turso
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"trankilou.fr/lassistanoque/backend/internal/adapter/database/orm"
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
)
|
||||
|
||||
type TursoToolRepository struct {
|
||||
db *sql.DB
|
||||
ToolTable orm.Table[domain.Tool]
|
||||
}
|
||||
|
||||
func NewTursoToolRepository(db *sql.DB) *TursoToolRepository {
|
||||
dialect := orm.NewSqliteDialect()
|
||||
ToolTable, err := orm.NewTable[domain.Tool](db, dialect, "tools")
|
||||
if err != nil {
|
||||
log.Fatalf("error creating lasebuche Tool table")
|
||||
}
|
||||
|
||||
return &TursoToolRepository{
|
||||
db: db,
|
||||
ToolTable: ToolTable,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TursoToolRepository) ListTools(userID string, teamID string) ([]*domain.Tool, error) {
|
||||
return r.ToolTable.Select(
|
||||
orm.WithWhere(
|
||||
"team_id=$1 and team_id in (select team_id from user_teams where user_id=$2)",
|
||||
teamID,
|
||||
userID,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *TursoToolRepository) GetToolByName(userID string, teamID string, name string) (*domain.Tool, error) {
|
||||
return r.ToolTable.SelectOne(
|
||||
"name=$1 and team_id=$2 and team_id in (select team_id from user_teams where user_id=$3)",
|
||||
name,
|
||||
teamID,
|
||||
userID,
|
||||
)
|
||||
}
|
||||
|
||||
func (r *TursoToolRepository) GetToolByID(userID string, teamID string, id string) (*domain.Tool, error) {
|
||||
return r.ToolTable.SelectOne(
|
||||
"id=$1 and team_id=$2 and team_id in (select team_id from user_teams where user_id=$3)",
|
||||
id,
|
||||
teamID,
|
||||
userID,
|
||||
)
|
||||
}
|
||||
|
||||
func (r *TursoToolRepository) CreateTool(userID string, Tool *domain.Tool) (*domain.Tool, error) {
|
||||
return r.ToolTable.Insert(Tool)
|
||||
}
|
||||
|
||||
func (r *TursoToolRepository) UpdateTool(userID string, Tool *domain.Tool) (*domain.Tool, error) {
|
||||
return r.ToolTable.Update(Tool)
|
||||
}
|
||||
|
||||
func (r *TursoToolRepository) DeleteTool(userID string, teamID string, name string) error {
|
||||
return r.ToolTable.DeleteWhere(
|
||||
"name=$1 and team_id=$2 and team_id in (select team_id from user_teams where user_id=$3)",
|
||||
name,
|
||||
teamID,
|
||||
userID,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package searxng
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
)
|
||||
|
||||
type SearxngConfig struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type SearxngTool struct {
|
||||
}
|
||||
|
||||
func (t *SearxngTool) Name() string {
|
||||
return "searxng"
|
||||
}
|
||||
|
||||
func (t *SearxngTool) Description() string {
|
||||
return "Search the web using a SearXNG instance. Returns a list of relevant results with title, URL, and snippet."
|
||||
}
|
||||
|
||||
func (t *SearxngTool) Params() []domain.ToolParam {
|
||||
return []domain.ToolParam{
|
||||
{JsonName: "url", Type: "string", Mandatory: true, Description: "Searxng URL"},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *SearxngTool) Definition(ctx context.Context) *domain.ToolDefinition {
|
||||
return &domain.ToolDefinition{
|
||||
Type: "function",
|
||||
Function: &domain.ToolFunction{
|
||||
Name: t.Name(),
|
||||
Description: t.Description(),
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"query": map[string]any{
|
||||
"type": "string",
|
||||
"description": "The search query",
|
||||
},
|
||||
"categories": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Comma-separated list of categories: general, news, images, science, it, social_media. Defaults to 'general'.",
|
||||
},
|
||||
"language": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Language code for results, e.g. 'fr', 'en'. Defaults to 'all'.",
|
||||
},
|
||||
},
|
||||
"required": []string{"query"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type searxngResponse struct {
|
||||
Results []searxngResult `json:"results"`
|
||||
}
|
||||
|
||||
type searxngResult struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Content string `json:"content"`
|
||||
Engine string `json:"engine"`
|
||||
}
|
||||
|
||||
func (t *SearxngTool) Execute(ctx context.Context, input []byte, configuration string) ([]byte, error) {
|
||||
|
||||
slog.Debug("[searxng]", "input", string(input))
|
||||
|
||||
var params struct {
|
||||
Query string `json:"query"`
|
||||
Categories string `json:"categories"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
if err := json.Unmarshal(input, ¶ms); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
var toolCfg SearxngConfig
|
||||
err := json.Unmarshal([]byte(configuration), &toolCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("searxng tool not configured: %w", err)
|
||||
}
|
||||
if toolCfg.URL == "" {
|
||||
return nil, fmt.Errorf("searxng tool config missing 'url' field")
|
||||
}
|
||||
baseURL := toolCfg.URL
|
||||
|
||||
categories := "general"
|
||||
if params.Categories != "" {
|
||||
categories = params.Categories
|
||||
}
|
||||
language := "all"
|
||||
if params.Language != "" {
|
||||
language = params.Language
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/search?q=%s&format=json&categories=%s&language=%s",
|
||||
strings.TrimRight(baseURL, "/"),
|
||||
url.QueryEscape(params.Query),
|
||||
url.QueryEscape(categories),
|
||||
url.QueryEscape(language),
|
||||
)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build request: %w", err)
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("searxng request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("searxng returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var searchResp searxngResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&searchResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
if len(searchResp.Results) == 0 {
|
||||
return []byte("No results found."), nil
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "Search results for \"%s\":\n\n", params.Query)
|
||||
for i, r := range searchResp.Results {
|
||||
fmt.Fprintf(&sb, "%d. %s\n", i+1, r.Title)
|
||||
fmt.Fprintf(&sb, " URL: %s\n", r.URL)
|
||||
if r.Content != "" {
|
||||
fmt.Fprintf(&sb, " %s\n", r.Content)
|
||||
}
|
||||
fmt.Fprintf(&sb, "\n")
|
||||
}
|
||||
|
||||
return []byte(sb.String()), nil
|
||||
}
|
||||
@@ -25,6 +25,10 @@ func (t *WeatherTool) Description() string {
|
||||
return "Get the current weather for a given location"
|
||||
}
|
||||
|
||||
func (t *WeatherTool) Params() []domain.ToolParam {
|
||||
return []domain.ToolParam{}
|
||||
}
|
||||
|
||||
func (t *WeatherTool) Definition(ctx context.Context) *domain.ToolDefinition {
|
||||
return &domain.ToolDefinition{
|
||||
Type: "function",
|
||||
@@ -45,7 +49,7 @@ func (t *WeatherTool) Definition(ctx context.Context) *domain.ToolDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *WeatherTool) Execute(ctx context.Context, input []byte) ([]byte, error) {
|
||||
func (t *WeatherTool) Execute(ctx context.Context, input []byte, configuration string) ([]byte, error) {
|
||||
|
||||
var params struct {
|
||||
Location string `json:"location"`
|
||||
|
||||
Reference in new issue
Block a user