Forge
csharpdeeb25a2
1namespace CasaField.Core;
2
3public sealed record SnPorts(string Mode, IReadOnlyDictionary<string, double> Ports, bool CenOpen);
4
5public sealed record CenTarget(
6 string Kind,
7 string ConceptId,
8 string? DocPath,
9 string? Section,
10 string? File,
11 int? Line,
12 int? LineEnd);
13
14public sealed record AnswerBlock(
15 string ConceptId,
16 string Text,
17 string Section,
18 string DocPath,
19 int Score,
20 string ClaimKind);
21
22public sealed record CenTask(
23 string Intent,
24 string Action,
25 IReadOnlyList<string> Concepts,
26 IReadOnlyList<CenTarget> Targets,
27 double Confidence,
28 string? QueryToDmn,
29 IReadOnlyDictionary<string, double> TaskBand,
30 IReadOnlyList<AnswerBlock> AnswerBlocks);
31
32public sealed record HotPathResult(
33 double WallMs,
34 int FieldVersion,
35 bool Stale,
36 DecodedGrid Decoded,
37 SnPorts Sn,
38 CenTask Cen,
39 IReadOnlyList<ClaimNavItem> ClaimsNav);
40
41public static class SnCenRuntime
42{
43 public static SnPorts Route(DecodedGrid decoded, IReadOnlyList<ClaimNavItem> nav, double gateThreshold = 0.35)
44 {
45 var conf = decoded.ConceptIds.Count > 0 ? 1.0 : 0.2;
46 var p2 = conf >= gateThreshold ? conf : conf * 0.5;
47 var ports = new Dictionary<string, double>
48 {
49 ["p1_dmn_to_sn"] = 1.0,
50 ["p2_sn_to_cen"] = p2,
51 ["p3_cen_to_dmn"] = conf >= gateThreshold ? 0.0 : 0.25,
52 };
53 var mode = p2 >= gateThreshold ? "execute" : "probe";
54 return new SnPorts(mode, ports, p2 >= gateThreshold);
55 }
56
57 public static CenTask Plan(
58 string? query,
59 DecodedGrid decoded,
60 SnPorts sn,
61 IReadOnlyList<ClaimNavItem> nav)
62 {
63 var words = Tokenize(query);
64 var hits = new List<(int Score, ClaimNavItem Item)>();
65
66 foreach (var item in nav)
67 {
68 var score = ScoreClaim(query ?? "", words, item);
69 if (score > 0)
70 hits.Add((score, item));
71 }
72
73 hits.Sort((a, b) => b.Score.CompareTo(a.Score));
74 var top = hits.Take(5).Select(h => h.Item).ToList();
75 if (top.Count == 0 && nav.Count > 0)
76 top = nav.Take(3).ToList();
77
78 var concepts = top.Select(t => t.ConceptId).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
79 var targets = BuildTargets(top);
80 var action = targets.Any(t => t.Kind == "code") ? "open_code"
81 : targets.Any(t => t.Kind == "kb") ? "open_kb"
82 : hits.Count > 0 ? "answer_from_memory"
83 : "answer_from_memory";
84
85 var answerBlocks = hits
86 .Take(3)
87 .Select(h => new AnswerBlock(
88 h.Item.ConceptId,
89 h.Item.Text,
90 h.Item.Section,
91 h.Item.DocPath,
92 h.Score,
93 h.Item.ClaimKind ?? "claim"))
94 .ToList();
95
96 var conf = sn.Ports.GetValueOrDefault("p2_sn_to_cen");
97 var taskBand = new Dictionary<string, double>
98 {
99 ["task.intent.query"] = string.IsNullOrEmpty(query) ? 0.0 : 1.0,
100 ["task.action.open_kb"] = targets.Any(t => t.Kind == "kb") ? 1.0 : 0.0,
101 ["task.action.open_code"] = targets.Any(t => t.Kind == "code") ? 1.0 : 0.0,
102 ["task.confidence"] = conf,
103 };
104
105 return new CenTask(
106 string.IsNullOrEmpty(query) ? "summarize" : "query",
107 action,
108 concepts,
109 targets,
110 conf,
111 hits.Count == 0 && !string.IsNullOrEmpty(query) ? $"clarify:{query[..Math.Min(120, query.Length)]}" : null,
112 taskBand,
113 answerBlocks);
114 }
115
116 private static List<string> Tokenize(string? query)
117 {
118 if (string.IsNullOrWhiteSpace(query))
119 return [];
120 return System.Text.RegularExpressions.Regex.Matches(query, @"[\w\u0400-\u04FF]+")
121 .Select(m => m.Value.ToLowerInvariant())
122 .Where(w => w.Length > 2)
123 .ToList();
124 }
125
126 private static int ScoreClaim(string query, List<string> words, ClaimNavItem item)
127 {
128 var score = 0;
129 var cid = item.ConceptId.ToLowerInvariant();
130 var sec = item.Section.ToLowerInvariant();
131 var text = item.Text.ToLowerInvariant();
132 var q = query.ToLowerInvariant();
133 if (cid.Contains(q, StringComparison.Ordinal) || q.Contains(cid, StringComparison.Ordinal))
134 score += 12;
135 foreach (var w in words)
136 {
137 if (cid.Contains(w, StringComparison.Ordinal)) score += 4;
138 if (sec.Contains(w, StringComparison.Ordinal)) score += 3;
139 if (text.Contains(w, StringComparison.Ordinal)) score += 2;
140 }
141 return score;
142 }
143
144 private static List<CenTarget> BuildTargets(IReadOnlyList<ClaimNavItem> items)
145 {
146 var targets = new List<CenTarget>();
147 foreach (var item in items)
148 {
149 targets.Add(new CenTarget("kb", item.ConceptId, item.DocPath, item.Section, null, null, null));
150 if (item.CodeAnchors is null)
151 continue;
152 foreach (var a in item.CodeAnchors)
153 {
154 targets.Add(new CenTarget("code", item.ConceptId, null, null, a.File, a.Line, a.LineEnd));
155 }
156 }
157 return targets;
158 }
159}
160
161public static class CasaFieldHotPath
162{
163 public static HotPathResult Run(string storeDirectory, string? query = null)
164 {
165 var sw = System.Diagnostics.Stopwatch.StartNew();
166 var fieldPath = Path.Combine(storeDirectory, "field_state.json");
167 var snapshot = FieldStateLoader.Load(fieldPath, storeDirectory);
168 if (snapshot.Grid is null)
169 throw new InvalidOperationException("field_state has no grid");
170
171 var decoded = GridDecoder.Decode(snapshot.Grid.Cells, CasaCanonical.FieldMinVotes);
172 var sn = SnCenRuntime.Route(decoded, snapshot.ClaimsNav);
173 var cen = SnCenRuntime.Plan(query, decoded, sn, snapshot.ClaimsNav);
174 sw.Stop();
175 return new HotPathResult(
176 Math.Round(sw.Elapsed.TotalMilliseconds, 2),
177 snapshot.FieldVersion,
178 snapshot.Stale,
179 decoded,
180 sn,
181 cen,
182 snapshot.ClaimsNav);
183 }
184}
185
View only · write via MCP/CIDE