feat: add document pipeline, ReAct agent, and knowledge core services

Document Pipeline (Team A):
- LibrarianService: document storage with filesystem backend, metadata
  persistence, child document hierarchy, collection management
- ChunkingService: recursive character text splitter with configurable
  chunk size/overlap, FlowProcessor pattern
- KnowledgeExtractService: combined relationship + definition extraction
  using prompt service and LLM, emits RDF triples and entity contexts
- KnowledgeCoreService: knowledge core CRUD with streaming export and
  flow-based loading

ReAct Agent (Team B):
- StreamingReActParser: state machine for parsing LLM output into
  Thought/Action/ActionInput/FinalAnswer sections
- Three MVP tools: KnowledgeQuery (GraphRAG), DocumentQuery (DocRAG),
  TriplesQuery with RequestResponse clients
- AgentService FlowProcessor with ReAct loop, tool execution, and
  streaming chunk responses (thought/observation/answer)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
elpresidank 2026-04-06 00:19:37 -05:00
parent 5ed3f0e2d8
commit f09ef4de45
18 changed files with 2145 additions and 2 deletions

View file

@ -0,0 +1,73 @@
/**
* Collection manager in-memory CRUD for document collections.
*
* Used by LibrarianService to manage collections per-user.
* MVP: purely in-memory, no persistence (state is persisted
* via the parent LibrarianService JSON snapshot).
*/
export interface CollectionEntry {
user: string;
collection: string;
name: string;
description: string;
tags: string[];
}
export class CollectionManager {
/** keyed by `${user}:${collection}` */
private collections = new Map<string, CollectionEntry>();
private key(user: string, collection: string): string {
return `${user}:${collection}`;
}
listCollections(user: string): CollectionEntry[] {
const result: CollectionEntry[] = [];
for (const entry of this.collections.values()) {
if (entry.user === user) {
result.push(entry);
}
}
return result;
}
getCollection(user: string, collection: string): CollectionEntry | undefined {
return this.collections.get(this.key(user, collection));
}
updateCollection(
user: string,
collection: string,
name: string,
description: string,
tags: string[],
): CollectionEntry {
const entry: CollectionEntry = { user, collection, name, description, tags };
this.collections.set(this.key(user, collection), entry);
return entry;
}
deleteCollection(user: string, collection: string): boolean {
return this.collections.delete(this.key(user, collection));
}
ensureCollectionExists(user: string, collection: string): CollectionEntry {
const existing = this.getCollection(user, collection);
if (existing) return existing;
return this.updateCollection(user, collection, collection, "", []);
}
/** Serialize to a plain array for JSON persistence. */
toJSON(): CollectionEntry[] {
return [...this.collections.values()];
}
/** Restore from a serialized array. */
loadFromJSON(entries: CollectionEntry[]): void {
this.collections.clear();
for (const entry of entries) {
this.collections.set(this.key(entry.user, entry.collection), entry);
}
}
}