amalfa 1.0.33 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/daemon/sonar-logic.ts +50 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amalfa",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"description": "Local-first knowledge graph engine for AI agents. Transforms markdown into searchable memory with MCP protocol.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/pjsvis/amalfa#readme",
|
|
@@ -197,14 +197,22 @@ export async function handleSearchAnalysis(
|
|
|
197
197
|
{
|
|
198
198
|
role: "system",
|
|
199
199
|
content:
|
|
200
|
-
'Analyze
|
|
200
|
+
'Analyze the user query. Extract the search intent, key entities, and any implicit filters. You MUST return valid JSON. Example: { "intent": "informational", "entities": ["vector"], "filters": {} }. Do not include any text outside the JSON object.',
|
|
201
201
|
},
|
|
202
202
|
{ role: "user", content: query },
|
|
203
203
|
],
|
|
204
204
|
{ temperature: 0.1, format: "json" },
|
|
205
205
|
);
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
const parsed = safeJsonParse(response.message.content);
|
|
208
|
+
if (!parsed) {
|
|
209
|
+
log.warn(
|
|
210
|
+
{ content: response.message.content },
|
|
211
|
+
"Failed to parse JSON response, using fallback",
|
|
212
|
+
);
|
|
213
|
+
return { intent: "search", entities: [query], filters: {} };
|
|
214
|
+
}
|
|
215
|
+
return parsed;
|
|
208
216
|
} catch (error) {
|
|
209
217
|
log.error({ error, query }, "Query analysis failed");
|
|
210
218
|
throw error;
|
|
@@ -250,7 +258,10 @@ export async function handleResultReranking(
|
|
|
250
258
|
|
|
251
259
|
const content = response.message.content;
|
|
252
260
|
try {
|
|
253
|
-
const rankings =
|
|
261
|
+
const rankings = safeJsonParse(content);
|
|
262
|
+
if (!rankings || !Array.isArray(rankings))
|
|
263
|
+
throw new Error("Invalid JSON");
|
|
264
|
+
|
|
254
265
|
return results.map((result, idx) => {
|
|
255
266
|
const ranking = rankings.find(
|
|
256
267
|
(r: { index: number }) => r.index === idx + 1,
|
|
@@ -283,19 +294,25 @@ export async function handleContextExtraction(
|
|
|
283
294
|
{
|
|
284
295
|
role: "system",
|
|
285
296
|
content:
|
|
286
|
-
"Extract the
|
|
297
|
+
"You are a helpful assistant. Extract the exact text snippet from the document that answers the query. Return ONLY the snippet text. If the answer is not found, return the most relevant paragraph.",
|
|
287
298
|
},
|
|
288
299
|
{
|
|
289
300
|
role: "user",
|
|
290
|
-
content: `Query: ${query}\nDocument
|
|
301
|
+
content: `Query: ${query}\n\nDocument Text:\n${result.content.slice(0, 4000)}`,
|
|
291
302
|
},
|
|
292
303
|
],
|
|
293
|
-
{ temperature: 0 },
|
|
304
|
+
{ temperature: 0.1 },
|
|
294
305
|
);
|
|
295
306
|
|
|
307
|
+
const snippet = response.message.content.trim();
|
|
308
|
+
|
|
309
|
+
// Fallback if model refuses to extract or returns empty
|
|
310
|
+
const finalSnippet =
|
|
311
|
+
snippet.length > 5 ? snippet : result.content.slice(0, 300).trim();
|
|
312
|
+
|
|
296
313
|
return {
|
|
297
314
|
id: result.id,
|
|
298
|
-
snippet:
|
|
315
|
+
snippet: finalSnippet,
|
|
299
316
|
};
|
|
300
317
|
} catch (error) {
|
|
301
318
|
log.error({ error, docId: result.id }, "Context extraction failed");
|
|
@@ -520,17 +537,11 @@ Return JSON: { "action": "SEARCH"|"READ"|"EXPLORE"|"FINISH", "query": "...", "no
|
|
|
520
537
|
nodeId?: string;
|
|
521
538
|
reasoning: string;
|
|
522
539
|
answer?: string;
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const match = content.match(/\{[\s\S]*\}/);
|
|
529
|
-
if (match) {
|
|
530
|
-
decision = JSON.parse(match[0]);
|
|
531
|
-
} else {
|
|
532
|
-
throw new Error("Could not parse JSON from response");
|
|
533
|
-
}
|
|
540
|
+
} | null = null;
|
|
541
|
+
|
|
542
|
+
decision = safeJsonParse(content);
|
|
543
|
+
if (!decision) {
|
|
544
|
+
throw new Error("Could not parse JSON from response");
|
|
534
545
|
}
|
|
535
546
|
output += `> **Reasoning:** ${decision.reasoning}\n\n`;
|
|
536
547
|
|
|
@@ -635,13 +646,7 @@ Return JSON: { "answered": true|false, "missing_info": "...", "final_answer": ".
|
|
|
635
646
|
missing_info: string;
|
|
636
647
|
final_answer: string;
|
|
637
648
|
};
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
audit = JSON.parse(resultSnippet);
|
|
641
|
-
} catch {
|
|
642
|
-
const match = resultSnippet.match(/\{[\s\S]*\}/);
|
|
643
|
-
audit = match ? JSON.parse(match[0]) : null;
|
|
644
|
-
}
|
|
649
|
+
const audit = safeJsonParse(resultSnippet) as AuditResult | null;
|
|
645
650
|
|
|
646
651
|
if (audit) {
|
|
647
652
|
if (!audit.answered) {
|
|
@@ -660,3 +665,23 @@ Return JSON: { "answered": true|false, "missing_info": "...", "final_answer": ".
|
|
|
660
665
|
|
|
661
666
|
return output;
|
|
662
667
|
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Helper to safely parse JSON from LLM responses, handling markdown blocks
|
|
671
|
+
*/
|
|
672
|
+
function safeJsonParse(content: string): any {
|
|
673
|
+
try {
|
|
674
|
+
return JSON.parse(content);
|
|
675
|
+
} catch {
|
|
676
|
+
// Try to extract JSON from markdown blocks
|
|
677
|
+
const match = content.match(/\{[\s\S]*\}/);
|
|
678
|
+
if (match) {
|
|
679
|
+
try {
|
|
680
|
+
return JSON.parse(match[0]);
|
|
681
|
+
} catch {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
}
|