feat: add parish-rag demo — RAG-enhanced NPC knowledge#486
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c1c6f591f2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| user: &str, | ||
| max_tokens: u32, | ||
| ) -> Result<String, String> { | ||
| let url = format!("{}/v1/chat/completions", base_url.trim_end_matches('/')); |
There was a problem hiding this comment.
Normalize chat base URL before appending the v1 route
Constructing the chat URL with format!("{}/v1/chat/completions", base_url.trim_end_matches('/')) breaks whenever callers pass a common OpenAI-style base URL that already ends in /v1 (for example http://localhost:11434/v1), producing /v1/v1/chat/completions and consistent 404s. This makes --llm fail for a realistic configuration; stripping a trailing /v1 before concatenation (as done elsewhere in the repo) avoids that regression.
Useful? React with 👍 / 👎.
| .map(|d| (cosine_similarity(query, &d.embedding), d)) | ||
| .collect(); | ||
| scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal)); | ||
| scored.truncate(k); |
There was a problem hiding this comment.
Return no hits when retrieval scores have no signal
The search path always truncates to k documents even when every cosine score is 0.0, which happens for empty/stopword-only hash queries; this injects arbitrary lore into the prompt despite no semantic match. In practice, queries like "the and of" return the first indexed docs with zero scores, degrading answer quality and making RAG output misleading. Filtering out non-positive scores (or short-circuiting when query magnitude is zero) would prevent false recalls.
Useful? React with 👍 / 👎.
Standalone crate showing how retrieval-augmented generation can ground
NPC dialogue in the parish's own lore (world locations, NPC bios,
festivals) instead of relying on hallucinated facts.
- Chunks mods/rundale/{world,npcs,festivals}.json into ~280 passages.
- Ships two embedders: a deterministic hashing-trick embedder for
offline/test runs, and an Ollama /api/embeddings client for semantic
retrieval.
- Cosine top-k search with a small LoreIndex.
- npc_knowledge_demo binary prints retrieved passages + baseline vs.
RAG-augmented system prompts; with --llm, calls an OpenAI-compatible
chat endpoint for both and prints the responses side by side.
- 31 unit tests; all deterministic and run without a network.
https://claude.ai/code/session_01Npr5LoSFHrwKoYsQJVUzd7
c1c6f59 to
0da2490
Compare
The corpus tests load mods/rundale/world.json using a relative path. The original path joined only 2 levels up (from parish-rag -> crates), but the crate is at parish/crates/parish-rag, requiring 3 levels to reach the repo root. Tests failed with 'No such file or directory' because the resolved path was incorrect during CI.
The send button was missing its aria-label attribute which provides contextual information to screen readers: "Waiting for response..." when streaming is active, "Type a message to send" when empty, or "Send message (Enter)" otherwise. This fixes the missing attribute from the PR. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Summary
Standalone
parish-ragcrate demonstrating how retrieval-augmented generation can ground NPC dialogue in the parish's own lore (world locations, NPC bios, festivals) instead of relying on the model's training data or the 4 hand-writtenknowledgebullets innpcs.json.The crate is a pattern demo, not a replacement for the existing keyword recall in
parish-npc::memory. No existing code was modified.What's in it
LoreIndex— in-memory vector store with cosine top-k search.AnyEmbedder— unified handle over two backends:HashEmbedder— deterministic hashing-trick embedder. Zero network, zero dependencies, reproducible test output.OllamaEmbedder— real/api/embeddingsclient (e.g.nomic-embed-text) for semantic retrieval.build_rundale_corpus(path)— chunksmods/rundale/{world,npcs,festivals}.jsoninto ~280 single-fact passages.format_recall_block(hits)— formats retrieved hits as a "KNOWLEDGE YOU RECALL" block to append to an NPC's system prompt.npc_knowledge_demobinary — runs a scripted set of questions, printing retrieved passages + baseline vs. RAG system prompts. With--llm, calls an OpenAI-compatible chat endpoint for both and prints the responses side by side.Try it
Sample offline output (Padraig, top-4):
Test plan
cargo test -p parish-rag— 31 tests, all deterministic, offline, pass.cargo clippy -p parish-rag --all-targets -- -D warnings— clean.cargo fmt -p parish-rag— formatted.cargo check --workspace --exclude parish-tauri— clean. (Tauri excluded only because of a pre-existinggdk-pixbuf-syssystem-library issue in this sandbox, unrelated to this change.)cargo run -p parish-rag --bin npc_knowledge_demo— demo runs end-to-end against shipped Rundale mod.nomic-embed-textinstalled.Files changed
Cargo.toml— addedcrates/parish-ragto workspace members.Cargo.lock— regenerated for the new crate.crates/parish-rag/— new crate (lib + bin + README + tests).No changes to
parish-core,parish-npc,parish-inference, or any existing code.https://claude.ai/code/session_01Npr5LoSFHrwKoYsQJVUzd7