V8 / 34b774036cd1
WebOperations.cs
Lilith/Core/WebResearch/WebOperations.cs
Changed in this versionCS
using System.Text.Json;
using System.Text.RegularExpressions;
namespace Lilith.Core.WebResearch;
internal static class WebOperations
{
internal static readonly string[] Names = ["web_search_operation", "web_fetch_operation", "web_check_answer_operation"];
internal static async Task<string> ExecuteAsync(IResearchBrowser browser, string name, IReadOnlyDictionary<string, string> inputs, CancellationToken ct)
{
string Get(string key) => inputs.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value) ? value : throw new ArgumentException("Missing web input: " + key);
switch (name)
{
case "web_search_operation":
string question = Get("question");
if (question.Length > 2000 || !int.TryParse(Get("source_count"), out int count) || count is < 1 or > 20) throw new ArgumentException("Question must be at most 2000 characters; source_count must be 1-20.");
return JsonSerializer.Serialize(await browser.SearchAsync(question, Math.Min(30, count * 3), ct), JsonOptions);
case "web_fetch_operation":
string url = PublicWeb.ValidateUri(Get("url")).AbsoluteUri;
try { return JsonSerializer.Serialize(await browser.ReadAsync(url, ct), JsonOptions); }
catch (Exception ex) when (ex is IOException or System.Net.Http.HttpRequestException or ArgumentException)
{ return JsonSerializer.Serialize(new WebPage(url, "", false, "", "Public page could not be read.", DateTimeOffset.UtcNow), JsonOptions); }
case "web_check_answer_operation":
using (var evidence = JsonDocument.Parse(Get("evidence")))
{
var data = evidence.RootElement;
var urls = data.GetProperty("items").EnumerateArray().Where(i => i.TryGetProperty("readable", out var readable) && bool.TryParse(readable.ToString(), out bool valid) && valid)
.Select(i => PublicWeb.ValidateUri(i.GetProperty("url").GetString()!).AbsoluteUri).ToHashSet(StringComparer.Ordinal);
int requested = data.GetProperty("requested").GetInt32(), attempts = data.GetProperty("attempted").GetInt32();
if (urls.Count == 0) return "I could not verify an answer from readable public sources. Search engines or pages may be unavailable, blocked, or require a browser interaction. No current answer is claimed. Sources read: 0 of " + requested + ".";
string answer = Get("answer");
var links = Regex.Matches(answer, @"\[[^\]]*\]\(([^\s)]+)\)");
if (links.Count == 0 || links.Cast<Match>().Any(m => !Uri.TryCreate(m.Groups[1].Value, UriKind.Absolute, out var uri) || !urls.Contains(uri.AbsoluteUri)))
throw new InvalidDataException("Synthesis cited no verified source links, or cited a page that was not read.");
return answer + $"\n\nSources read: {urls.Count} of {requested} requested ({attempts} page attempts).";
}
default: throw new ArgumentException("Unknown research operation.");
}
}
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
}