fix(memory): reject dangling relations (#4477)

Signed-off-by: King Star <mcxin.y@gmail.com>
This commit is contained in:
King Star 2026-09-02 23:04:34 +08:00 • committed by GitHub
commit 48723c95dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 0 deletions

View file

@ -72,6 +72,7 @@ Example:
- `to` (string): Target entity name
- `relationType` (string): Relationship type in active voice
- Skips duplicate relations
- Fails if either the source or target entity doesn't exist
- **add_observations**
- Add new observations to existing entities

View file

@ -99,6 +99,38 @@ describe('KnowledgeGraphManager', () => {
expect(graph.relations).toHaveLength(1);
});
it('should reject relations from non-existent entities', async () => {
await manager.createEntities([
{ name: 'Alice', entityType: 'person', observations: [] },
]);
await expect(
manager.createRelations([
{ from: 'Ghost', to: 'Alice', relationType: 'knows' },
])
).rejects.toThrow('Entity with name Ghost not found');
const graph = await manager.readGraph();
expect(graph.relations).toHaveLength(0);
});
it('should reject relation batches that reference non-existent target entities', async () => {
await manager.createEntities([
{ name: 'Alice', entityType: 'person', observations: [] },
{ name: 'Bob', entityType: 'person', observations: [] },
]);
await expect(
manager.createRelations([
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
{ from: 'Alice', to: 'Ghost', relationType: 'knows' },
])
).rejects.toThrow('Entity with name Ghost not found');
const graph = await manager.readGraph();
expect(graph.relations).toHaveLength(0);
});
it('should handle empty relation arrays', async () => {
const newRelations = await manager.createRelations([]);
expect(newRelations).toHaveLength(0);

View file

@ -151,6 +151,17 @@ export class KnowledgeGraphManager {
async createRelations(relations: Relation[]): Promise<Relation[]> {
const graph = await this.loadGraph();
const entityNames = new Set(graph.entities.map(e => e.name));
relations.forEach(r => {
if (!entityNames.has(r.from)) {
throw new Error(`Entity with name ${r.from} not found`);
}
if (!entityNames.has(r.to)) {
throw new Error(`Entity with name ${r.to} not found`);
}
});
const newRelations = relations.filter(r => !graph.relations.some(existingRelation =>
existingRelation.from === r.from &&
existingRelation.to === r.to &&