Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/memory/__tests__/knowledge-graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,4 +515,51 @@ describe('KnowledgeGraphManager', () => {
expect(result.relations[0]).not.toHaveProperty('type');
});
});

describe('loadGraph validation', () => {
it('skips corrupt entities instead of crashing search', async () => {
const lines = [
JSON.stringify({ type: 'entity', name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }),
JSON.stringify({ type: 'entity', name: 'Broken', observations: ['missing entityType'] }),
JSON.stringify({ type: 'entity', name: 'BadObs', entityType: 'person', observations: ['ok', null] }),
];
await fs.writeFile(testFilePath, lines.join('\n') + '\n');

const graph = await manager.readGraph();
expect(graph.entities).toHaveLength(1);
expect(graph.entities[0].name).toBe('Alice');

// searchNodes must not throw even though the file contains corrupt entries
const result = await manager.searchNodes('Acme');
expect(result.entities).toHaveLength(1);
expect(result.entities[0].name).toBe('Alice');
});

it('skips corrupt relations', async () => {
const lines = [
JSON.stringify({ type: 'entity', name: 'Alice', entityType: 'person', observations: [] }),
JSON.stringify({ type: 'relation', from: 'Alice', to: 'Bob' }), // missing relationType
JSON.stringify({ type: 'relation', from: 'Alice', to: 'Bob', relationType: 'knows' }),
];
await fs.writeFile(testFilePath, lines.join('\n') + '\n');

const graph = await manager.readGraph();
expect(graph.entities).toHaveLength(1);
expect(graph.relations).toHaveLength(1);
expect(graph.relations[0].relationType).toBe('knows');
});

it('skips malformed JSON lines', async () => {
const lines = [
JSON.stringify({ type: 'entity', name: 'Alice', entityType: 'person', observations: [] }),
'{this is not valid json',
JSON.stringify({ type: 'entity', name: 'Bob', entityType: 'person', observations: [] }),
];
await fs.writeFile(testFilePath, lines.join('\n') + '\n');

const graph = await manager.readGraph();
expect(graph.entities).toHaveLength(2);
expect(graph.entities.map(e => e.name)).toEqual(['Alice', 'Bob']);
});
});
});
55 changes: 39 additions & 16 deletions src/memory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,24 +74,47 @@ export class KnowledgeGraphManager {
try {
const data = await fs.readFile(this.memoryFilePath, "utf-8");
const lines = data.split("\n").filter(line => line.trim() !== "");
return lines.reduce((graph: KnowledgeGraph, line) => {
const item = JSON.parse(line);
if (item.type === "entity") {
graph.entities.push({
name: item.name,
entityType: item.entityType,
observations: item.observations
});
const graph: KnowledgeGraph = { entities: [], relations: [] };

for (const line of lines) {
let item: unknown;
try {
item = JSON.parse(line);
} catch {
console.error("Skipping malformed line in memory file");
continue;
}
if (item.type === "relation") {
graph.relations.push({
from: item.from,
to: item.to,
relationType: item.relationType
});

if (typeof item !== "object" || item === null) {
console.error("Skipping non-object line in memory file");
continue;
}

const record = item as Record<string, unknown>;
if (record.type === "entity") {
const parsed = EntitySchema.safeParse(item);
if (parsed.success) {
graph.entities.push(parsed.data);
} else {
console.error(
"Skipping invalid entity in memory file:",
parsed.error.issues.map(issue => `${issue.path.join(".")}: ${issue.message}`).join(", ")
);
}
} else if (record.type === "relation") {
const parsed = RelationSchema.safeParse(item);
if (parsed.success) {
graph.relations.push(parsed.data);
} else {
console.error(
"Skipping invalid relation in memory file:",
parsed.error.issues.map(issue => `${issue.path.join(".")}: ${issue.message}`).join(", ")
);
}
}
return graph;
}, { entities: [], relations: [] });
}

return graph;
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
return { entities: [], relations: [] };
Expand Down