fix(mnemopi): dropped object-shaped fact coercion
- Unwrapped extractor fact items from known text fields instead of coercing arbitrary objects with String(). - Dropped unrecognized object entries so derived fact indexes cannot persist literal [object Object] rows. - Added a parseFacts regression covering mixed strings, wrapped objects, and junk timeline objects. Fixes #4649
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed extractor JSON parsing so object-shaped fact, instruction, preference, and timeline items are unwrapped from known text fields or dropped instead of persisting literal `[object Object]` rows. ([#4649](https://github.com/can1357/oh-my-pi/issues/4649))
|
||||
|
||||
## [16.3.7] - 2026-07-05
|
||||
|
||||
### Added
|
||||
|
||||
@@ -85,6 +85,7 @@ function stripFence(raw: string): string {
|
||||
const FLAT_FACT_LIMIT = 5;
|
||||
const STRUCTURED_CATEGORY_LIMIT = 5;
|
||||
const STRING_CATEGORY_KEYS = ["facts", "instructions", "preferences", "timelines"] as const;
|
||||
const FACT_TEXT_FIELD_KEYS = ["fact", "text", "content", "value", "statement"] as const;
|
||||
|
||||
/** Parsed knowledge-graph edge emitted by the extractor LLM. */
|
||||
export interface ExtractedKgTriple {
|
||||
@@ -118,8 +119,20 @@ function normalizeFactArray(items: unknown): string[] {
|
||||
}
|
||||
const out: string[] = [];
|
||||
for (const item of items) {
|
||||
if (item !== null && item !== undefined && String(item).trim() !== "") {
|
||||
const normalized = normalizeFact(String(item));
|
||||
let text: string | null = null;
|
||||
if (typeof item === "string") {
|
||||
text = item.trim();
|
||||
} else if (isRecord(item)) {
|
||||
for (const key of FACT_TEXT_FIELD_KEYS) {
|
||||
const candidate = item[key];
|
||||
if (typeof candidate === "string" && candidate.trim() !== "") {
|
||||
text = candidate.trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (text !== null && text !== "") {
|
||||
const normalized = normalizeFact(text);
|
||||
if (normalized !== "") {
|
||||
out.push(normalized);
|
||||
if (out.length >= STRUCTURED_CATEGORY_LIMIT) break;
|
||||
|
||||
@@ -52,6 +52,22 @@ describe("structured extraction", () => {
|
||||
expect(parseFacts("NO_FACTS")).toEqual([]);
|
||||
});
|
||||
|
||||
it("drops unrecognized object facts instead of stringifying them", () => {
|
||||
const modelJson = JSON.stringify({
|
||||
facts: [{ fact: "The user prefers tabs over spaces" }, { nested: {} }, "The user likes concise replies."],
|
||||
instructions: [{ text: "Always include verification details" }],
|
||||
preferences: [{ value: "Prefers dark mode" }],
|
||||
timelines: [{ subject: "release", predicate: "on", object: "2026-08-01" }],
|
||||
});
|
||||
|
||||
expect(parseFacts(modelJson)).toEqual([
|
||||
"The user prefers tabs over spaces",
|
||||
"The user likes concise replies",
|
||||
"Always include verification details",
|
||||
"Prefers dark mode",
|
||||
]);
|
||||
});
|
||||
|
||||
it("treats a valid empty structured extraction as no facts", () => {
|
||||
expect(parseFacts('{"facts": [], "instructions": [], "preferences": [], "timelines": [], "kg": []}')).toEqual([]);
|
||||
expect(
|
||||
|
||||
Reference in New Issue
Block a user