mirror of
https://github.com/hanzoai/brain.git
synced 2026-10-30 18:46:38 +00:00
Closed every remaining π‘ / π΅ row in PARITY.md. Each algorithm FortΓ©mi ships now has a real implementation in @hanzo/bot-memory v2.0.0 with matching ports in hanzo-memory (Python), bot-go (Go), and hanzoai/mcp (Rust). Cross-runtime tests: 121 TS + 53 Python + 58 Go + 38 Rust = 270 passing. Landed under packages/memory/ Retrieval & ranking fusion RRF (k=20 BEIR) + RSF (Weaviate v1.24) + adaptive k + adaptive FTS/semantic weights rerank MMR (Carbonell & Goldstein 1998), cosine dedup chunk-aware deduplication script Unicode script detection (Latin/CJK/emoji/Cyrillic/ Arabic/Hebrew/Greek/Devanagari) fts feature-flag matrix, CJK bigrams, emoji trigrams, websearch_to_tsquery parser, SQLite FTS5 MATCH renderer embed embedding registry, asymmetric E5 prefixes, Matryoshka truncation, coarse-dim picker two-stage coarse β fine retrieval via MRL federated cross-archive search with RRF fusion multi-memory X-Hanzo-Memory routing (X-Fortemi-Memory alias) filters composable tag/temporal/collection/scope predicates temporal UUIDv7 floor/ceiling + named ranges spatial Haversine + bbox + in-box (PostGIS fallback) Knowledge & graph graph normalize β SNN β PFNET-β β Louvain maintenance pipeline skos W3C SKOS concept tree (broader/narrower closures, Turtle export) fair Dublin Core XML + JSON-LD doc-types document type registry + auto-detect (filename/MIME/ content sniff) captions WebVTT / SRT / RTTM renderers sprite thumbnail sprite-sheet WebVTT cue builder code-ast language-aware syntactic chunker email RFC 2822 / MIME parser (multipart, qp, base64, HTML strip) exif EXIF parser (camera / time / GPS) Operational events EventBus + SSE renderer + webhook HMAC signer tokenizer BPE-style estimator + truncate-to-budget eval MRR / recall@k / precision@k / NDCG@k / benchmark hardware tier autodetect + per-tier model recommender circuit-breaker closed / open / half-open retry exponential backoff with full jitter range HTTP Range parser + Content-Range formatter tus TUS v1.0.0 (Create / Patch / metadata / checksum) Crypto address wallet-style content-addressable ids (hanzo: / mm:) mmpke01 multi-recipient envelope parser/builder + magic-byte sniff Inference (packages/memory/inference/) index InferenceBackend trait + registry slug provider-qualified routing (provider:model:tag) capabilities capability flags + task-based selector runtime-config hot-swap config (db_override β env β default) mock deterministic backend (for tests) few-shot ICL prompt builder (3-5 examples capped) refinement Self-Refine / ReAct / Reflexion patterns link-types rule + LLM link classifier (11 canonical types) vision VisionBackend trait + Ollama default transcription Whisper-compatible HTTP client diarization pyannote-compatible HTTP client + alignSpeakers / relabel gliner GLiNER zero-shot NER HTTP client Extraction (packages/memory/extract/) index registry + dispatcher text native UTF-8 passthrough email RFC 2822 adapter spreadsheet CSV/TSV pure-JS; xlsx via registerXlsxDecoder archive ZIP central-directory pure-JS reader exif-adapter EXIF for images code tree-sitter-style syntactic chunker pdf PdfSidecar HTTP adapter factory image VisionBackend-backed adapter factory audio TranscriptionBackend-backed adapter factory video keyframe + scene + transcript adapter factory 3d multi-view 3D model adapter factory media-optimize ffmpeg variants (faststart / web-compat / 720p) adapter factory structured JSON-schema structured-extract adapter factory summarizer fast/standard cascade adapter factory sprite-adapter thumbnail sprite-sheet adapter factory Cross-runtime ports hanzo-memory (Python) 53 tests, ports of fusion / rerank / dedup / script / fts / embed / temporal / captions / tokenizer / eval / spatial / range / address / graph / doc-types / circuit / retry / slug / runtime-config / link-types bot-go (Go) 58 tests, same algorithm set ported with standard-library-only primitives hanzoai/mcp::brain (Rust) 38 tests, same algorithm set; sha2 added as dependency (blake3 stand-in) PARITY.md is now 100% green (β in-monorepo + π’ sibling-repo). No π΅ placeholders remain.
118 lines
4.5 KiB
TypeScript
118 lines
4.5 KiB
TypeScript
/**
|
|
* AI-refinement loops:
|
|
*
|
|
* - Self-Refine (Madaan et al. 2023): iterative self-critique
|
|
* - ReAct (Yao et al. 2023): Thought β Action β Observation
|
|
* - Reflexion (Shinn et al. 2023): episodic memory of past failures
|
|
*
|
|
* Each pattern is a pure orchestrator β it calls back into an
|
|
* `InferenceBackend` (or any function that returns a string).
|
|
*/
|
|
|
|
export type Generator = (prompt: string) => Promise<string>;
|
|
|
|
// ββ Self-Refine ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
export interface SelfRefineOpts {
|
|
/** Max critique-revise rounds (default 3). */
|
|
rounds?: number;
|
|
/** Optional stop predicate β when true, stop refining. */
|
|
stopWhen?: (output: string, critique: string) => boolean;
|
|
}
|
|
|
|
export async function selfRefine(
|
|
initial: string,
|
|
generate: Generator,
|
|
opts: SelfRefineOpts = {},
|
|
): Promise<{ output: string; rounds: number }> {
|
|
const rounds = opts.rounds ?? 3;
|
|
let output = initial;
|
|
for (let r = 0; r < rounds; r++) {
|
|
const critique = await generate(`Critique the following draft. Be specific.\n\n${output}\n\nCritique:`);
|
|
if (opts.stopWhen?.(output, critique)) return { output, rounds: r };
|
|
const revised = await generate(
|
|
`Revise the draft using the critique. Output only the revised version.\n\nDraft:\n${output}\n\nCritique:\n${critique}\n\nRevised:`,
|
|
);
|
|
output = revised.trim();
|
|
}
|
|
return { output, rounds };
|
|
}
|
|
|
|
// ββ ReAct ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
export interface ReactStep {
|
|
thought: string;
|
|
action: string;
|
|
observation: string;
|
|
}
|
|
|
|
export interface ReactOpts {
|
|
maxSteps?: number;
|
|
/** Action runner β caller maps action strings to side effects. */
|
|
runAction: (action: string) => Promise<string>;
|
|
/** True when goal reached. */
|
|
done: (steps: ReactStep[]) => boolean;
|
|
}
|
|
|
|
export async function react(
|
|
goal: string,
|
|
generate: Generator,
|
|
opts: ReactOpts,
|
|
): Promise<{ steps: ReactStep[]; final: string }> {
|
|
const max = opts.maxSteps ?? 8;
|
|
const steps: ReactStep[] = [];
|
|
for (let i = 0; i < max; i++) {
|
|
const trace = steps
|
|
.map((s, j) => `Thought ${j + 1}: ${s.thought}\nAction ${j + 1}: ${s.action}\nObservation ${j + 1}: ${s.observation}`)
|
|
.join("\n");
|
|
const next = await generate(
|
|
`You are solving: ${goal}\n${trace}\n\nWhat is the next Thought / Action?`,
|
|
);
|
|
const thoughtMatch = next.match(/Thought[^:]*:\s*(.+?)(?:Action|$)/s);
|
|
const actionMatch = next.match(/Action[^:]*:\s*(.+?)(?:Observation|$)/s);
|
|
const thought = thoughtMatch?.[1]?.trim() ?? next.trim();
|
|
const action = actionMatch?.[1]?.trim() ?? "FINISH";
|
|
if (action === "FINISH") {
|
|
return { steps, final: thought };
|
|
}
|
|
const observation = await opts.runAction(action);
|
|
steps.push({ thought, action, observation });
|
|
if (opts.done(steps)) break;
|
|
}
|
|
const final = await generate(
|
|
`Goal: ${goal}\n\nTrace:\n${steps.map((s) => `T: ${s.thought}\nA: ${s.action}\nO: ${s.observation}`).join("\n")}\n\nFinal answer:`,
|
|
);
|
|
return { steps, final: final.trim() };
|
|
}
|
|
|
|
// ββ Reflexion βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
export interface ReflexionOpts {
|
|
/** Max attempts before giving up. */
|
|
maxAttempts?: number;
|
|
/** Evaluator: returns true if the output is acceptable. */
|
|
evaluate: (output: string) => Promise<boolean>;
|
|
}
|
|
|
|
export async function reflexion(
|
|
task: string,
|
|
generate: Generator,
|
|
opts: ReflexionOpts,
|
|
): Promise<{ output: string; attempts: number; reflections: string[] }> {
|
|
const max = opts.maxAttempts ?? 3;
|
|
const reflections: string[] = [];
|
|
let output = await generate(task);
|
|
if (await opts.evaluate(output)) return { output, attempts: 1, reflections };
|
|
for (let i = 1; i < max; i++) {
|
|
const reflection = await generate(
|
|
`Reflect on why the following attempt failed and what to do differently.\n\nTask: ${task}\nAttempt: ${output}\n\nReflection:`,
|
|
);
|
|
reflections.push(reflection.trim());
|
|
output = await generate(
|
|
`Task: ${task}\nPast reflections:\n${reflections.map((r, j) => `${j + 1}. ${r}`).join("\n")}\n\nNew attempt:`,
|
|
);
|
|
if (await opts.evaluate(output)) return { output, attempts: i + 1, reflections };
|
|
}
|
|
return { output, attempts: max, reflections };
|
|
}
|