feat: Serper Web Search als Agent-Tool
- ToolService: serper_web_search Tool-Definition + ExecuteSerperWebSearch() Google-Suche via Serper.dev API mit Titel, Link, Snippet Knowledge Graph + Answer Box + organische Ergebnisse - AgentRecord: UseSerperSearch Property (Default: false) - DB-Migration: use_serper_search Spalte - AnthropicService + ChatService: useSerperSearch Parameter durchreichen - ChatEndpoint + AgentEndpoint: useSerperSearch in API - Frontend: Checkbox, Badge (Serper Search), laden/speichern - Aktivierung: Agent-Checkbox + SerperApiKey in Server-Einstellungen
This commit is contained in:
1 parent
558ded1ed5
commit
192f41e91e
11 files changed
+150
-12
No files matched your search
@@ -66,6 +66,7 @@ public static class AgentEndpoint
|
|||||||
a.UseMemory,
|
a.UseMemory,
|
||||||
a.UseAutoContext,
|
a.UseAutoContext,
|
||||||
a.UseGitHubSearch,
|
a.UseGitHubSearch,
|
||||||
|
a.UseSerperSearch,
|
||||||
a.UseWebSearch,
|
a.UseWebSearch,
|
||||||
a.WebSearchVersion,
|
a.WebSearchVersion,
|
||||||
a.AllowUserChoice,
|
a.AllowUserChoice,
|
||||||
@@ -123,6 +124,7 @@ public static class AgentEndpoint
|
|||||||
UseMemory = !body.TryGetProperty("useMemory", out var um) || um.GetBoolean(),
|
UseMemory = !body.TryGetProperty("useMemory", out var um) || um.GetBoolean(),
|
||||||
UseAutoContext = !body.TryGetProperty("useAutoContext", out var uac) || uac.GetBoolean(),
|
UseAutoContext = !body.TryGetProperty("useAutoContext", out var uac) || uac.GetBoolean(),
|
||||||
UseGitHubSearch = body.TryGetProperty("useGitHubSearch", out var ughs) && ughs.GetBoolean(),
|
UseGitHubSearch = body.TryGetProperty("useGitHubSearch", out var ughs) && ughs.GetBoolean(),
|
||||||
|
UseSerperSearch = body.TryGetProperty("useSerperSearch", out var usps) && usps.GetBoolean(),
|
||||||
UseWebSearch = body.TryGetProperty("webSearchVersion", out var wsv) && !string.IsNullOrEmpty(wsv.GetString()),
|
UseWebSearch = body.TryGetProperty("webSearchVersion", out var wsv) && !string.IsNullOrEmpty(wsv.GetString()),
|
||||||
WebSearchVersion = body.TryGetProperty("webSearchVersion", out var wsv2) ? wsv2.GetString() ?? "" : "",
|
WebSearchVersion = body.TryGetProperty("webSearchVersion", out var wsv2) ? wsv2.GetString() ?? "" : "",
|
||||||
AllowUserChoice = !body.TryGetProperty("allowUserChoice", out var auc) || auc.GetBoolean(),
|
AllowUserChoice = !body.TryGetProperty("allowUserChoice", out var auc) || auc.GetBoolean(),
|
||||||
@@ -166,6 +168,7 @@ public static class AgentEndpoint
|
|||||||
if (body.TryGetProperty("useMemory", out var um)) agent.UseMemory = um.GetBoolean();
|
if (body.TryGetProperty("useMemory", out var um)) agent.UseMemory = um.GetBoolean();
|
||||||
if (body.TryGetProperty("useAutoContext", out var uac)) agent.UseAutoContext = uac.GetBoolean();
|
if (body.TryGetProperty("useAutoContext", out var uac)) agent.UseAutoContext = uac.GetBoolean();
|
||||||
if (body.TryGetProperty("useGitHubSearch", out var ughs)) agent.UseGitHubSearch = ughs.GetBoolean();
|
if (body.TryGetProperty("useGitHubSearch", out var ughs)) agent.UseGitHubSearch = ughs.GetBoolean();
|
||||||
|
if (body.TryGetProperty("useSerperSearch", out var usps)) agent.UseSerperSearch = usps.GetBoolean();
|
||||||
if (body.TryGetProperty("webSearchVersion", out var wsv))
|
if (body.TryGetProperty("webSearchVersion", out var wsv))
|
||||||
{
|
{
|
||||||
agent.WebSearchVersion = wsv.GetString() ?? "";
|
agent.WebSearchVersion = wsv.GetString() ?? "";
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ public static class ChatEndpoint
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Standard: Anthropic
|
// Standard: Anthropic
|
||||||
await anthropic.StreamChat(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, ctx.Workspace, sessionId, agentId: ctx.AgentId, userId: userId, useMemory: ctx.UseMemory, webSearchVersion: ctx.WebSearchVersion, maxIterations: ctx.MaxIterations, toolChoice: effectiveToolChoice, review: ctx.Review, finalizer: ctx.Finalizer, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: ctx.PlanMode, useAutoContext: ctx.Agent?.UseAutoContext ?? true, useGitHubSearch: ctx.Agent?.UseGitHubSearch ?? false, workCt: workCt, hardCt: hardCt, sseCt: sseCt, remoteMgr: remoteMgr);
|
await anthropic.StreamChat(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, ctx.Workspace, sessionId, agentId: ctx.AgentId, userId: userId, useMemory: ctx.UseMemory, webSearchVersion: ctx.WebSearchVersion, maxIterations: ctx.MaxIterations, toolChoice: effectiveToolChoice, review: ctx.Review, finalizer: ctx.Finalizer, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: ctx.PlanMode, useAutoContext: ctx.Agent?.UseAutoContext ?? true, useGitHubSearch: ctx.Agent?.UseGitHubSearch ?? false, useSerperSearch: ctx.Agent?.UseSerperSearch ?? false, workCt: workCt, hardCt: hardCt, sseCt: sseCt, remoteMgr: remoteMgr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public class AgentRecord
|
|||||||
public bool UseMemory { get; set; } = true;
|
public bool UseMemory { get; set; } = true;
|
||||||
public bool UseAutoContext { get; set; } = true;
|
public bool UseAutoContext { get; set; } = true;
|
||||||
public bool UseGitHubSearch { get; set; }
|
public bool UseGitHubSearch { get; set; }
|
||||||
|
public bool UseSerperSearch { get; set; }
|
||||||
public bool UseWebSearch { get; set; }
|
public bool UseWebSearch { get; set; }
|
||||||
public string WebSearchVersion { get; set; } = "";
|
public string WebSearchVersion { get; set; } = "";
|
||||||
public bool AllowUserChoice { get; set; } = true;
|
public bool AllowUserChoice { get; set; } = true;
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ public class AnthropicService
|
|||||||
bool planMode = false,
|
bool planMode = false,
|
||||||
bool useAutoContext = true,
|
bool useAutoContext = true,
|
||||||
bool useGitHubSearch = false,
|
bool useGitHubSearch = false,
|
||||||
|
bool useSerperSearch = false,
|
||||||
CancellationToken workCt = default,
|
CancellationToken workCt = default,
|
||||||
CancellationToken hardCt = default,
|
CancellationToken hardCt = default,
|
||||||
CancellationToken sseCt = default,
|
CancellationToken sseCt = default,
|
||||||
@@ -178,7 +179,7 @@ public class AnthropicService
|
|||||||
payload["system"] = systemPrompt;
|
payload["system"] = systemPrompt;
|
||||||
|
|
||||||
var hasRemote = remoteMgr?.GetBySessionId(sessionId) != null;
|
var hasRemote = remoteMgr?.GetBySessionId(sessionId) != null;
|
||||||
AddToolsToPayload(payload, workspace, agentId, useMemory, webSearchVersion, toolChoice, iterations, allowUserChoice, planMode, includeRemote: hasRemote, includeGitHubSearch: useGitHubSearch && _toolService.HasGitHubToken);
|
AddToolsToPayload(payload, workspace, agentId, useMemory, webSearchVersion, toolChoice, iterations, allowUserChoice, planMode, includeRemote: hasRemote, includeGitHubSearch: useGitHubSearch && _toolService.HasGitHubToken, includeSerperSearch: useSerperSearch && _toolService.HasSerperKey);
|
||||||
|
|
||||||
var client = CreateClient();
|
var client = CreateClient();
|
||||||
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
|
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
|
||||||
@@ -1620,13 +1621,13 @@ public class AnthropicService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fügt Tool-Definitionen und WebSearch zum Payload hinzu.
|
/// Fügt Tool-Definitionen und WebSearch zum Payload hinzu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void AddToolsToPayload(Dictionary<string, object> payload, string workspace, string agentId, bool useMemory, string webSearchVersion, string toolChoice, int iteration, bool allowUserChoice = false, bool planMode = false, bool includeRemote = false, bool includeGitHubSearch = false)
|
private void AddToolsToPayload(Dictionary<string, object> payload, string workspace, string agentId, bool useMemory, string webSearchVersion, string toolChoice, int iteration, bool allowUserChoice = false, bool planMode = false, bool includeRemote = false, bool includeGitHubSearch = false, bool includeSerperSearch = false)
|
||||||
{
|
{
|
||||||
var hasWorkspace = !string.IsNullOrEmpty(workspace);
|
var hasWorkspace = !string.IsNullOrEmpty(workspace);
|
||||||
var includeMemory = useMemory && _memoryService != null && !string.IsNullOrEmpty(agentId);
|
var includeMemory = useMemory && _memoryService != null && !string.IsNullOrEmpty(agentId);
|
||||||
var includeMemorySearch = _vectorSearch != null && _vectorSearch.GetEmbeddingCount() > 0;
|
var includeMemorySearch = _vectorSearch != null && _vectorSearch.GetEmbeddingCount() > 0;
|
||||||
var includeSendToSession = _db != null && _chatService != null;
|
var includeSendToSession = _db != null && _chatService != null;
|
||||||
var hasTools = hasWorkspace || includeMemory || includeRemote || includeMemorySearch || includeSendToSession || includeGitHubSearch;
|
var hasTools = hasWorkspace || includeMemory || includeRemote || includeMemorySearch || includeSendToSession || includeGitHubSearch || includeSerperSearch;
|
||||||
var hasWebSearch = !string.IsNullOrEmpty(webSearchVersion);
|
var hasWebSearch = !string.IsNullOrEmpty(webSearchVersion);
|
||||||
|
|
||||||
if (!hasTools && !hasWebSearch) return;
|
if (!hasTools && !hasWebSearch) return;
|
||||||
@@ -1634,7 +1635,7 @@ public class AnthropicService
|
|||||||
var tools = new List<object>();
|
var tools = new List<object>();
|
||||||
if (hasTools)
|
if (hasTools)
|
||||||
{
|
{
|
||||||
var allTools = _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: allowUserChoice, includeRemote: includeRemote, includeMemorySearch: includeMemorySearch, includeSendToSession: includeSendToSession, includeGitHubSearch: includeGitHubSearch);
|
var allTools = _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: allowUserChoice, includeRemote: includeRemote, includeMemorySearch: includeMemorySearch, includeSendToSession: includeSendToSession, includeGitHubSearch: includeGitHubSearch, includeSerperSearch: includeSerperSearch);
|
||||||
if (planMode)
|
if (planMode)
|
||||||
allTools = _toolService.FilterReadOnlyTools(allTools);
|
allTools = _toolService.FilterReadOnlyTools(allTools);
|
||||||
tools.AddRange(allTools);
|
tools.AddRange(allTools);
|
||||||
|
|||||||
@@ -80,11 +80,12 @@ public class ChatService
|
|||||||
var hasWorkspace = !string.IsNullOrEmpty(workspace);
|
var hasWorkspace = !string.IsNullOrEmpty(workspace);
|
||||||
var includeMemory = agent.UseMemory && !string.IsNullOrEmpty(agent.AgentId);
|
var includeMemory = agent.UseMemory && !string.IsNullOrEmpty(agent.AgentId);
|
||||||
var includeGitHubSearch = agent.UseGitHubSearch && _toolService.HasGitHubToken;
|
var includeGitHubSearch = agent.UseGitHubSearch && _toolService.HasGitHubToken;
|
||||||
var hasTools = hasWorkspace || includeMemory || includeGitHubSearch;
|
var includeSerperSearch = agent.UseSerperSearch && _toolService.HasSerperKey;
|
||||||
|
var hasTools = hasWorkspace || includeMemory || includeGitHubSearch || includeSerperSearch;
|
||||||
var toolChoice = hasTools ? "any" : null;
|
var toolChoice = hasTools ? "any" : null;
|
||||||
|
|
||||||
var toolDefs = hasTools
|
var toolDefs = hasTools
|
||||||
? _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: agent.AllowUserChoice, includeRemote: hasRemoteConnection, includeGitHubSearch: includeGitHubSearch)
|
? _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory, includeUserChoice: agent.AllowUserChoice, includeRemote: hasRemoteConnection, includeGitHubSearch: includeGitHubSearch, includeSerperSearch: includeSerperSearch)
|
||||||
: Array.Empty<object>();
|
: Array.Empty<object>();
|
||||||
var toolNames = toolDefs.Length > 0
|
var toolNames = toolDefs.Length > 0
|
||||||
? JsonSerializer.Serialize(toolDefs.Select(td =>
|
? JsonSerializer.Serialize(toolDefs.Select(td =>
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ public partial class DatabaseService
|
|||||||
agent.CreatedAt = now;
|
agent.CreatedAt = now;
|
||||||
agent.UpdatedAt = now;
|
agent.UpdatedAt = now;
|
||||||
await conn.ExecuteAsync(@"
|
await conn.ExecuteAsync(@"
|
||||||
INSERT INTO agents (agent_id, user_id, name, avatar, avatar_small, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_auto_context, use_github_search, use_web_search, web_search_version, allow_user_choice, task_instruction, avatar_scale, avatar_offset_x, avatar_offset_y, is_deleted, created_at, updated_at)
|
INSERT INTO agents (agent_id, user_id, name, avatar, avatar_small, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_auto_context, use_github_search, use_serper_search, use_web_search, web_search_version, allow_user_choice, task_instruction, avatar_scale, avatar_offset_x, avatar_offset_y, is_deleted, created_at, updated_at)
|
||||||
VALUES (@AgentId, @UserId, @Name, @Avatar, @AvatarSmall, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseAutoContext, @UseGitHubSearch, @UseWebSearch, @WebSearchVersion, @AllowUserChoice, @TaskInstruction, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, 0, @CreatedAt, @UpdatedAt)
|
VALUES (@AgentId, @UserId, @Name, @Avatar, @AvatarSmall, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseAutoContext, @UseGitHubSearch, @UseSerperSearch, @UseWebSearch, @WebSearchVersion, @AllowUserChoice, @TaskInstruction, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, 0, @CreatedAt, @UpdatedAt)
|
||||||
", agent);
|
", agent);
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ public partial class DatabaseService
|
|||||||
UPDATE agents SET name = @Name, avatar = @Avatar, avatar_small = @AvatarSmall, description = @Description,
|
UPDATE agents SET name = @Name, avatar = @Avatar, avatar_small = @AvatarSmall, description = @Description,
|
||||||
model = @Model, tool_choice = @ToolChoice,
|
model = @Model, tool_choice = @ToolChoice,
|
||||||
custom_commands = @CustomCommands, auto_commit = @AutoCommit,
|
custom_commands = @CustomCommands, auto_commit = @AutoCommit,
|
||||||
use_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory, use_auto_context = @UseAutoContext, use_github_search = @UseGitHubSearch, use_web_search = @UseWebSearch, web_search_version = @WebSearchVersion,
|
use_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory, use_auto_context = @UseAutoContext, use_github_search = @UseGitHubSearch, use_serper_search = @UseSerperSearch, use_web_search = @UseWebSearch, web_search_version = @WebSearchVersion,
|
||||||
allow_user_choice = @AllowUserChoice, task_instruction = @TaskInstruction,
|
allow_user_choice = @AllowUserChoice, task_instruction = @TaskInstruction,
|
||||||
avatar_scale = @AvatarScale, avatar_offset_x = @AvatarOffsetX, avatar_offset_y = @AvatarOffsetY,
|
avatar_scale = @AvatarScale, avatar_offset_x = @AvatarOffsetX, avatar_offset_y = @AvatarOffsetY,
|
||||||
is_public = @IsPublic, updated_at = @UpdatedAt
|
is_public = @IsPublic, updated_at = @UpdatedAt
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ public partial class DatabaseService
|
|||||||
TryAddColumn(conn, "agents", "use_memory", "INTEGER NOT NULL DEFAULT 1");
|
TryAddColumn(conn, "agents", "use_memory", "INTEGER NOT NULL DEFAULT 1");
|
||||||
TryAddColumn(conn, "agents", "use_auto_context", "INTEGER NOT NULL DEFAULT 1");
|
TryAddColumn(conn, "agents", "use_auto_context", "INTEGER NOT NULL DEFAULT 1");
|
||||||
TryAddColumn(conn, "agents", "use_github_search", "INTEGER NOT NULL DEFAULT 0");
|
TryAddColumn(conn, "agents", "use_github_search", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
TryAddColumn(conn, "agents", "use_serper_search", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
|
||||||
TryAddColumn(conn, "messages", "deleted_on", "TEXT");
|
TryAddColumn(conn, "messages", "deleted_on", "TEXT");
|
||||||
TryAddColumn(conn, "agents", "use_web_search", "INTEGER NOT NULL DEFAULT 0");
|
TryAddColumn(conn, "agents", "use_web_search", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
|||||||
+125
-2
@@ -33,6 +33,7 @@ public class ToolService
|
|||||||
|
|
||||||
private int ExecuteTimeoutSeconds => _config.GetValue<int>("ExecuteTimeout", 180);
|
private int ExecuteTimeoutSeconds => _config.GetValue<int>("ExecuteTimeout", 180);
|
||||||
public bool HasGitHubToken => !string.IsNullOrWhiteSpace(_config["GitHubToken"]);
|
public bool HasGitHubToken => !string.IsNullOrWhiteSpace(_config["GitHubToken"]);
|
||||||
|
public bool HasSerperKey => !string.IsNullOrWhiteSpace(_config["SerperApiKey"]);
|
||||||
public const string AskUserChoiceToolName = "ask_user_choice";
|
public const string AskUserChoiceToolName = "ask_user_choice";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,7 +44,7 @@ public class ToolService
|
|||||||
{
|
{
|
||||||
"read_file", "list_files", "glob", "grep", "execute", "todo_write",
|
"read_file", "list_files", "glob", "grep", "execute", "todo_write",
|
||||||
"memory_read", "memory_write", "memory_list", "memory_search",
|
"memory_read", "memory_write", "memory_list", "memory_search",
|
||||||
"remote_execute", "github_code_search", AskUserChoiceToolName
|
"remote_execute", "github_code_search", "serper_web_search", AskUserChoiceToolName
|
||||||
};
|
};
|
||||||
|
|
||||||
public object[] FilterReadOnlyTools(object[] tools)
|
public object[] FilterReadOnlyTools(object[] tools)
|
||||||
@@ -63,7 +64,7 @@ public class ToolService
|
|||||||
return filtered.ToArray();
|
return filtered.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false, bool includeUserChoice = true, bool includeRemote = false, bool includeMemorySearch = false, bool includeSendToSession = false, bool includeGitHubSearch = false)
|
public object[] GetToolDefinitions(bool includeFileTools = false, bool includeMemory = false, bool includeUserChoice = true, bool includeRemote = false, bool includeMemorySearch = false, bool includeSendToSession = false, bool includeGitHubSearch = false, bool includeSerperSearch = false)
|
||||||
{
|
{
|
||||||
var allTools = new List<object>();
|
var allTools = new List<object>();
|
||||||
if (includeUserChoice)
|
if (includeUserChoice)
|
||||||
@@ -80,6 +81,8 @@ public class ToolService
|
|||||||
allTools.Add(GetSendToSessionTool());
|
allTools.Add(GetSendToSessionTool());
|
||||||
if (includeGitHubSearch)
|
if (includeGitHubSearch)
|
||||||
allTools.Add(GetGitHubCodeSearchTool());
|
allTools.Add(GetGitHubCodeSearchTool());
|
||||||
|
if (includeSerperSearch)
|
||||||
|
allTools.Add(GetSerperWebSearchTool());
|
||||||
return allTools.ToArray();
|
return allTools.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,6 +817,10 @@ public class ToolService
|
|||||||
{
|
{
|
||||||
return await ExecuteGitHubCodeSearch(input, cancellationToken);
|
return await ExecuteGitHubCodeSearch(input, cancellationToken);
|
||||||
}
|
}
|
||||||
|
case "serper_web_search":
|
||||||
|
{
|
||||||
|
return await ExecuteSerperWebSearch(input, cancellationToken);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return $"Fehler: Unbekanntes Tool: {name}";
|
return $"Fehler: Unbekanntes Tool: {name}";
|
||||||
}
|
}
|
||||||
@@ -929,4 +936,120 @@ public class ToolService
|
|||||||
return $"GitHub-Suche fehlgeschlagen: {ex.Message}";
|
return $"GitHub-Suche fehlgeschlagen: {ex.Message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Serper Web Search ──
|
||||||
|
|
||||||
|
private object GetSerperWebSearchTool()
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
name = "serper_web_search",
|
||||||
|
description = "Durchsucht das Internet via Google (Serper.dev API). Gibt strukturierte Suchergebnisse zurück mit Titel, Link und Snippet. Unterstützt alle Google-Suchoperatoren (z.B. exakte Phrasen mit Anführungszeichen, site:, filetype:). Nutze dies für Webrecherche, aktuelle Informationen oder das Finden spezifischer Inhalte im Internet.",
|
||||||
|
input_schema = new
|
||||||
|
{
|
||||||
|
type = "object",
|
||||||
|
properties = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["query"] = new { type = "string", description = "Suchbegriff (unterstützt Google-Suchsyntax, z.B. '\"exakte Phrase\"', 'site:github.com', 'filetype:pdf')" },
|
||||||
|
["num"] = new { type = "integer", description = "Anzahl Ergebnisse (1-100, Default: 10)" }
|
||||||
|
},
|
||||||
|
required = new[] { "query" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> ExecuteSerperWebSearch(JsonElement input, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var apiKey = _config["SerperApiKey"] ?? "";
|
||||||
|
if (string.IsNullOrWhiteSpace(apiKey))
|
||||||
|
return "Fehler: Kein SerperApiKey konfiguriert. Bitte in den Server-Einstellungen hinterlegen.";
|
||||||
|
|
||||||
|
var query = input.GetProperty("query").GetString() ?? "";
|
||||||
|
if (string.IsNullOrWhiteSpace(query))
|
||||||
|
return "Fehler: Suchbegriff (query) darf nicht leer sein.";
|
||||||
|
|
||||||
|
var num = input.TryGetProperty("num", out var numProp) ? Math.Clamp(numProp.GetInt32(), 1, 100) : 10;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = _httpFactory.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.Add("X-API-KEY", apiKey);
|
||||||
|
|
||||||
|
var requestBody = JsonSerializer.Serialize(new { q = query, num = num });
|
||||||
|
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await client.PostAsync("https://google.serper.dev/search", content, ct);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorBody = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
return $"Serper API Fehler {(int)response.StatusCode}: {errorBody}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
var doc = JsonDocument.Parse(json);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Knowledge Graph (wenn vorhanden)
|
||||||
|
if (root.TryGetProperty("knowledgeGraph", out var kg))
|
||||||
|
{
|
||||||
|
if (kg.TryGetProperty("title", out var kgTitle))
|
||||||
|
sb.AppendLine($"## {kgTitle.GetString()}");
|
||||||
|
if (kg.TryGetProperty("description", out var kgDesc))
|
||||||
|
sb.AppendLine(kgDesc.GetString());
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer Box (wenn vorhanden)
|
||||||
|
if (root.TryGetProperty("answerBox", out var ab))
|
||||||
|
{
|
||||||
|
sb.AppendLine("### Direkte Antwort");
|
||||||
|
if (ab.TryGetProperty("title", out var abTitle))
|
||||||
|
sb.AppendLine($"**{abTitle.GetString()}**");
|
||||||
|
if (ab.TryGetProperty("answer", out var abAnswer))
|
||||||
|
sb.AppendLine(abAnswer.GetString());
|
||||||
|
else if (ab.TryGetProperty("snippet", out var abSnippet))
|
||||||
|
sb.AppendLine(abSnippet.GetString());
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Organische Ergebnisse
|
||||||
|
if (root.TryGetProperty("organic", out var organic))
|
||||||
|
{
|
||||||
|
var results = organic.EnumerateArray().ToArray();
|
||||||
|
sb.AppendLine($"**{results.Length} Ergebnisse:**");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (var item in results)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
var title = item.TryGetProperty("title", out var t) ? t.GetString() : "";
|
||||||
|
var link = item.TryGetProperty("link", out var l) ? l.GetString() : "";
|
||||||
|
var snippet = item.TryGetProperty("snippet", out var s) ? s.GetString() : "";
|
||||||
|
|
||||||
|
sb.AppendLine($"### {index}. {title}");
|
||||||
|
sb.AppendLine($"🔗 {link}");
|
||||||
|
if (!string.IsNullOrEmpty(snippet))
|
||||||
|
sb.AppendLine(snippet);
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb.Length == 0)
|
||||||
|
sb.AppendLine("Keine Ergebnisse gefunden.");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
return "Serper-Suche abgebrochen (Timeout).";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return $"Serper-Suche fehlgeschlagen: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -245,6 +245,10 @@
|
|||||||
<input type="checkbox" id="agentUseGitHubSearch">
|
<input type="checkbox" id="agentUseGitHubSearch">
|
||||||
GitHub Code Search <span class="checkbox-hint">(Öffentlichen Code auf GitHub durchsuchen)</span>
|
GitHub Code Search <span class="checkbox-hint">(Öffentlichen Code auf GitHub durchsuchen)</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="agentUseSerperSearch">
|
||||||
|
Serper Web Search <span class="checkbox-hint">(Google-Suche via Serper.dev API)</span>
|
||||||
|
</label>
|
||||||
<label>Web-Suche</label>
|
<label>Web-Suche</label>
|
||||||
<select id="agentWebSearchVersion">
|
<select id="agentWebSearchVersion">
|
||||||
<option value="">Deaktiviert</option>
|
<option value="">Deaktiviert</option>
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ function buildAgentCard(agent, skills) {
|
|||||||
if (agent.useMemory) badges.push('<span class="badge badge-feature" title="Zugriff auf Erinnerungen (Memory-Tools)"><i class="fa fa-database"></i> Erinnerungen</span>');
|
if (agent.useMemory) badges.push('<span class="badge badge-feature" title="Zugriff auf Erinnerungen (Memory-Tools)"><i class="fa fa-database"></i> Erinnerungen</span>');
|
||||||
if (agent.useAutoContext) badges.push('<span class="badge badge-feature" title="Relevante Erinnerungen werden automatisch abgerufen"><i class="fa fa-magic"></i> Auto-Kontext</span>');
|
if (agent.useAutoContext) badges.push('<span class="badge badge-feature" title="Relevante Erinnerungen werden automatisch abgerufen"><i class="fa fa-magic"></i> Auto-Kontext</span>');
|
||||||
if (agent.useGitHubSearch) badges.push('<span class="badge badge-feature" title="Öffentlichen Code auf GitHub durchsuchen"><i class="fa fa-github"></i> GitHub Search</span>');
|
if (agent.useGitHubSearch) badges.push('<span class="badge badge-feature" title="Öffentlichen Code auf GitHub durchsuchen"><i class="fa fa-github"></i> GitHub Search</span>');
|
||||||
|
if (agent.useSerperSearch) badges.push('<span class="badge badge-feature" title="Google-Suche via Serper.dev API"><i class="fa fa-google"></i> Serper Search</span>');
|
||||||
if (agent.webSearchVersion) {
|
if (agent.webSearchVersion) {
|
||||||
const wsYear = agent.webSearchVersion.includes('2025') ? 'Version 2025' : 'v2026';
|
const wsYear = agent.webSearchVersion.includes('2025') ? 'Version 2025' : 'v2026';
|
||||||
badges.push(`<span class="badge badge-feature" title="Web-Suche - ${wsYear}"><i class="fa fa-search"></i> Web-Suche</span>`);
|
badges.push(`<span class="badge badge-feature" title="Web-Suche - ${wsYear}"><i class="fa fa-search"></i> Web-Suche</span>`);
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ registerPage('agent', {
|
|||||||
const useMemoryEl = document.getElementById('agentUseMemory');
|
const useMemoryEl = document.getElementById('agentUseMemory');
|
||||||
const useAutoContextEl = document.getElementById('agentUseAutoContext');
|
const useAutoContextEl = document.getElementById('agentUseAutoContext');
|
||||||
const useGitHubSearchEl = document.getElementById('agentUseGitHubSearch');
|
const useGitHubSearchEl = document.getElementById('agentUseGitHubSearch');
|
||||||
|
const useSerperSearchEl = document.getElementById('agentUseSerperSearch');
|
||||||
const webSearchVersionEl = document.getElementById('agentWebSearchVersion');
|
const webSearchVersionEl = document.getElementById('agentWebSearchVersion');
|
||||||
const avatarPreview = document.getElementById('avatarPreview');
|
const avatarPreview = document.getElementById('avatarPreview');
|
||||||
const saveBtn = document.getElementById('agentSaveBtn');
|
const saveBtn = document.getElementById('agentSaveBtn');
|
||||||
@@ -96,6 +97,7 @@ registerPage('agent', {
|
|||||||
useMemoryEl.checked = agent.useMemory !== false;
|
useMemoryEl.checked = agent.useMemory !== false;
|
||||||
useAutoContextEl.checked = agent.useAutoContext !== false;
|
useAutoContextEl.checked = agent.useAutoContext !== false;
|
||||||
useGitHubSearchEl.checked = agent.useGitHubSearch || false;
|
useGitHubSearchEl.checked = agent.useGitHubSearch || false;
|
||||||
|
useSerperSearchEl.checked = agent.useSerperSearch || false;
|
||||||
webSearchVersionEl.value = agent.webSearchVersion || '';
|
webSearchVersionEl.value = agent.webSearchVersion || '';
|
||||||
isPublicEl.checked = agent.isPublic || false;
|
isPublicEl.checked = agent.isPublic || false;
|
||||||
deleteBtn.hidden = false;
|
deleteBtn.hidden = false;
|
||||||
@@ -139,6 +141,7 @@ registerPage('agent', {
|
|||||||
useMemory: useMemoryEl.checked,
|
useMemory: useMemoryEl.checked,
|
||||||
useAutoContext: useAutoContextEl.checked,
|
useAutoContext: useAutoContextEl.checked,
|
||||||
useGitHubSearch: useGitHubSearchEl.checked,
|
useGitHubSearch: useGitHubSearchEl.checked,
|
||||||
|
useSerperSearch: useSerperSearchEl.checked,
|
||||||
webSearchVersion: webSearchVersionEl.value,
|
webSearchVersion: webSearchVersionEl.value,
|
||||||
avatar: _agentAvatarEditor.getImage(),
|
avatar: _agentAvatarEditor.getImage(),
|
||||||
avatarScale: _agentAvatarEditor.getScale(),
|
avatarScale: _agentAvatarEditor.getScale(),
|
||||||
|
|||||||
Reference in new issue
Block a user