Ship Assistent disk persist, park LLM, and memory UI cleanup.
Split the extension into partials, persist chats on the data volume, park/warm the chat model around Generate, and drop dual raw/persona dump paths. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+152
-30
@@ -13,6 +13,7 @@ namespace Mrleo1nid.SwarmAssistent;
|
||||
public partial class SwarmAssistentExtension
|
||||
{
|
||||
const int MaxCivitaiHopsFallback = 2;
|
||||
const int MaxToolHopsFallback = 4;
|
||||
|
||||
List<JObject> BuildOllamaMessages(string packName, bool includeBase, string contextJson, JArray userMessages, string extraSystem = null, string personaId = null, IEnumerable<string> skillIds = null)
|
||||
{
|
||||
@@ -139,12 +140,12 @@ public partial class SwarmAssistentExtension
|
||||
Logs.Debug($"Assistent memory seed: {ex.Message}");
|
||||
}
|
||||
|
||||
string retrieveQuery = BuildRetrieveQuery(userMessages, contextJson);
|
||||
string retrieveQuery = BuildRetrieveQuery(userMessages, contextJson, packName);
|
||||
JArray hits = [];
|
||||
try
|
||||
{
|
||||
int topK = Config.LoadAssistant(pid)["memory_top_k"]?.Value<int?>() ?? 10;
|
||||
hits = await Memory.RetrieveAsync(root, retrieveQuery, topK, embed, Config.PersonaExtendsChain(pid));
|
||||
AssistentMemory.RetrieveOptions opt = MemoryRetrieveOptions(pid);
|
||||
hits = await Memory.RetrieveAsync(root, retrieveQuery, opt.TopK, embed, Config.PersonaExtendsChain(pid), opt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -156,7 +157,9 @@ public partial class SwarmAssistentExtension
|
||||
JArray civitaiResults = [];
|
||||
string reply = "";
|
||||
JObject lastRaw = null;
|
||||
int maxHops = CfgInt("max_civitai_hops", MaxCivitaiHopsFallback);
|
||||
int maxHops = Math.Max(CfgInt("max_civitai_hops", MaxCivitaiHopsFallback), CfgInt("max_tool_hops", MaxToolHopsFallback));
|
||||
HashSet<string> hopDone = new(StringComparer.OrdinalIgnoreCase);
|
||||
var chain = Config.PersonaExtendsChain(pid);
|
||||
for (int hop = 0; hop < maxHops; hop++)
|
||||
{
|
||||
if (onHopStart is not null)
|
||||
@@ -166,44 +169,37 @@ public partial class SwarmAssistentExtension
|
||||
(reply, lastRaw) = await CallOllamaChat(root, modelName, messages, stream: onDelta is not null, onDelta, pid);
|
||||
JObject patch = TryParsePatch(reply);
|
||||
await ApplyMemoryActions(root, patch, embed, pid);
|
||||
if (hop + 1 >= maxHops || !WantsCivitaiSearch(patch))
|
||||
if (hop + 1 >= maxHops)
|
||||
{
|
||||
break;
|
||||
}
|
||||
string query = ExtractSearchQuery(patch);
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
string tool = NextToolHop(patch);
|
||||
if (string.IsNullOrWhiteSpace(tool))
|
||||
{
|
||||
break;
|
||||
}
|
||||
JObject search = await AssistentSearchCivitai(session, query, 8);
|
||||
if (search["error"] is not null)
|
||||
(string follow, JArray civitaiHop) = await RunToolHop(session, root, embed, pid, chain, patch, tool, hopDone);
|
||||
if (follow is null)
|
||||
{
|
||||
messages.Add(new JObject { ["role"] = "assistant", ["content"] = reply });
|
||||
messages.Add(new JObject
|
||||
{
|
||||
["role"] = "user",
|
||||
["content"] = $"Civitai search failed: {search["error"]}. Continue without download — use only available_loras from context.",
|
||||
});
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (civitaiHop is { Count: > 0 })
|
||||
{
|
||||
civitaiResults = civitaiHop;
|
||||
}
|
||||
civitaiResults = search["results"] as JArray ?? [];
|
||||
messages.Add(new JObject { ["role"] = "assistant", ["content"] = reply });
|
||||
messages.Add(new JObject
|
||||
{
|
||||
["role"] = "user",
|
||||
["content"] =
|
||||
"Civitai search results (JSON). Prefer `krea_likely: true`. Do NOT download yourself — the UI shows Confirm cards. " +
|
||||
"Pick useful LoRAs from results or available_loras, emit a normal patch (prompt/loras). " +
|
||||
"Omit search_civitai from actions unless you need a different query.\n```json\n" +
|
||||
civitaiResults.ToString(Newtonsoft.Json.Formatting.None) + "\n```",
|
||||
});
|
||||
messages.Add(new JObject { ["role"] = "user", ["content"] = follow });
|
||||
}
|
||||
return (reply, lastRaw, civitaiResults);
|
||||
}
|
||||
|
||||
static string BuildRetrieveQuery(JArray userMessages, string contextJson)
|
||||
static string BuildRetrieveQuery(JArray userMessages, string contextJson, string packName = null)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
if (!string.IsNullOrWhiteSpace(packName))
|
||||
{
|
||||
sb.Append(packName).Append(' ');
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(contextJson))
|
||||
{
|
||||
try
|
||||
@@ -216,7 +212,7 @@ public partial class SwarmAssistentExtension
|
||||
}
|
||||
if (ctx["enabled_loras"] is JArray en)
|
||||
{
|
||||
foreach (JToken t in en.Take(8))
|
||||
foreach (JToken t in en.Take(12))
|
||||
{
|
||||
string n = t?["name"]?.ToString() ?? t?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(n))
|
||||
@@ -229,23 +225,149 @@ public partial class SwarmAssistentExtension
|
||||
{
|
||||
sb.Append("krea ").Append(ctx["krea_profile"]).Append(' ');
|
||||
}
|
||||
string aspect = ctx["aspect"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(aspect))
|
||||
{
|
||||
sb.Append(aspect).Append(' ');
|
||||
}
|
||||
string prompt = ctx["prompt"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
sb.Append(prompt.Length > 400 ? prompt[..400] : prompt).Append(' ');
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
foreach (JToken msg in (userMessages ?? []).Reverse().Take(2))
|
||||
foreach (JToken msg in (userMessages ?? []).Reverse().Take(3))
|
||||
{
|
||||
if (msg is JObject mo && string.Equals(mo["role"]?.ToString(), "user", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sb.Append(mo["content"]?.ToString()).Append(' ');
|
||||
string c = mo["content"]?.ToString() ?? "";
|
||||
sb.Append(c.Length > 500 ? c[..500] : c).Append(' ');
|
||||
}
|
||||
}
|
||||
string q = CollapseWs(sb.ToString());
|
||||
return string.IsNullOrWhiteSpace(q) ? "krea2 prompting" : q;
|
||||
}
|
||||
|
||||
AssistentMemory.RetrieveOptions MemoryRetrieveOptions(string pid)
|
||||
{
|
||||
JObject a = Config.LoadAssistant(pid) ?? new JObject();
|
||||
AssistentMemory.RetrieveOptions opt = new()
|
||||
{
|
||||
TopK = a["memory_top_k"]?.Value<int?>() ?? 10,
|
||||
MinScore = a["memory_min_score"]?.Value<float?>() ?? 0.32f,
|
||||
ApplyQuotas = true,
|
||||
};
|
||||
if (a["memory_quotas"] is JObject quotas)
|
||||
{
|
||||
Dictionary<string, int> d = new(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (JProperty p in quotas.Properties())
|
||||
{
|
||||
d[p.Name] = p.Value?.Value<int?>() ?? 2;
|
||||
}
|
||||
opt.Quotas = d;
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
|
||||
async Task<(string follow, JArray civitai)> RunToolHop(
|
||||
Session session,
|
||||
string root,
|
||||
string embed,
|
||||
string pid,
|
||||
IEnumerable<string> chain,
|
||||
JObject patch,
|
||||
string tool,
|
||||
HashSet<string> hopDone)
|
||||
{
|
||||
if (tool == "memory_get")
|
||||
{
|
||||
JArray got = [];
|
||||
foreach (JToken t in patch["memories"] as JArray ?? [])
|
||||
{
|
||||
if (t is not JObject mo)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string kind = mo["kind"]?.ToString() ?? "note";
|
||||
string key = mo["key"]?.ToString() ?? "";
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string sig = $"get:{kind}:{key}";
|
||||
if (!hopDone.Add(sig))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
JObject row = Memory.Get(kind, key, chain);
|
||||
got.Add(row ?? new JObject { ["kind"] = kind, ["key"] = key, ["missing"] = true });
|
||||
}
|
||||
if (got.Count == 0)
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
return (
|
||||
"memory_get results (JSON). Use these facts; omit memory_get unless you need a different key.\n```json\n"
|
||||
+ got.ToString(Newtonsoft.Json.Formatting.None) + "\n```",
|
||||
null);
|
||||
}
|
||||
if (tool == "memory_search")
|
||||
{
|
||||
string q = ExtractMemoryQuery(patch);
|
||||
if (string.IsNullOrWhiteSpace(q) || !hopDone.Add("search:" + q))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
string kind = patch["memory_kind"]?.ToString();
|
||||
int topK = Config.LoadAssistant(pid)["memory_top_k"]?.Value<int?>() ?? 10;
|
||||
JArray rows = await Memory.SearchAsync(root, q, kind, topK, embed, chain);
|
||||
return (
|
||||
"memory_search results (JSON, hybrid FTS+vector). Omit memory_search unless you need a different query.\n```json\n"
|
||||
+ rows.ToString(Newtonsoft.Json.Formatting.None) + "\n```",
|
||||
null);
|
||||
}
|
||||
if (tool == "lookup_tags")
|
||||
{
|
||||
string q = ExtractTagQuery(patch);
|
||||
if (string.IsNullOrWhiteSpace(q) || !hopDone.Add("tags:" + q))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
int lim = Config.LoadAssistant(pid)["tag_lookup_limit"]?.Value<int?>() ?? 20;
|
||||
JArray tags = Memory.LookupTags(q, lim);
|
||||
return (
|
||||
"lookup_tags results from Danbooru csv (canonical name, aliases, post_count). Krea prompts stay natural prose — use this to check spelling/aliases, do not dump tag soup.\n```json\n"
|
||||
+ tags.ToString(Newtonsoft.Json.Formatting.None) + "\n```",
|
||||
null);
|
||||
}
|
||||
if (tool == "civitai")
|
||||
{
|
||||
string query = ExtractSearchQuery(patch);
|
||||
if (string.IsNullOrWhiteSpace(query) || !hopDone.Add("civitai:" + query))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
JObject search = await AssistentSearchCivitai(session, query, 8);
|
||||
if (search["error"] is not null)
|
||||
{
|
||||
return ($"Civitai search failed: {search["error"]}. Continue without download — use only available_loras from context.", null);
|
||||
}
|
||||
JArray civitaiResults = search["results"] as JArray ?? [];
|
||||
return (
|
||||
"Civitai search results (JSON). Prefer `krea_likely: true`. Do NOT download yourself — the UI shows Confirm cards. " +
|
||||
"Pick useful LoRAs from results or available_loras, emit a normal patch (prompt/loras). " +
|
||||
"Omit search_civitai from actions unless you need a different query.\n```json\n" +
|
||||
civitaiResults.ToString(Newtonsoft.Json.Formatting.None) + "\n```",
|
||||
civitaiResults);
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
static string InjectMemoryHits(string contextJson, JArray hits, JObject exact = null)
|
||||
{
|
||||
JObject ctx;
|
||||
|
||||
Reference in New Issue
Block a user