Packages
nous
0.15.4
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.17
0.12.16
0.12.15
0.12.14
0.12.13
0.12.12
0.12.11
0.12.9
0.12.7
0.12.6
0.12.5
0.12.3
0.12.2
0.12.0
0.11.3
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.5.0
AI agent framework for Elixir with multi-provider LLM support
Current section
Files
Jump to
Current section
Files
lib/nous/plugins/knowledge_base.ex
defmodule Nous.Plugins.KnowledgeBase do
@moduledoc """
Plugin for LLM-compiled knowledge base with wiki-style entries.
Provides tools for agents to ingest documents, compile wiki entries,
search and query the knowledge base, and run health checks.
## Usage
# Minimal — ETS store
agent = Agent.new("openai:gpt-4",
plugins: [Nous.Plugins.KnowledgeBase],
deps: %{kb_config: %{store: Nous.KnowledgeBase.Store.ETS, kb_id: "my_kb"}}
)
# With embeddings for semantic search
agent = Agent.new("openai:gpt-4",
plugins: [Nous.Plugins.KnowledgeBase],
deps: %{
kb_config: %{
store: Nous.KnowledgeBase.Store.ETS,
kb_id: "my_kb",
embedding: Nous.Memory.Embedding.OpenAI,
embedding_opts: %{api_key: "sk-..."}
}
}
)
## Composition with Memory Plugin
The KB plugin composes cleanly with `Nous.Plugins.Memory`:
agent = Agent.new("openai:gpt-4",
plugins: [Nous.Plugins.Memory, Nous.Plugins.KnowledgeBase],
deps: %{
memory_config: %{store: Nous.Memory.Store.ETS},
kb_config: %{store: Nous.KnowledgeBase.Store.ETS, kb_id: "my_kb"}
}
)
## Configuration (via `deps[:kb_config]`)
**Required:**
* `:store` - Store backend module (e.g. `Nous.KnowledgeBase.Store.ETS`)
**Optional — store:**
* `:store_opts` - Options passed to `store.init/1`
* `:kb_id` - Namespace for this knowledge base
**Optional — embedding:**
* `:embedding` - Embedding provider module (reuses `Nous.Memory.Embedding.*`)
* `:embedding_opts` - Options passed to embedding provider
**Optional — auto-injection:**
* `:auto_inject` - Auto-inject relevant KB entries before each request (default: true)
* `:inject_strategy` - `:first_only` (default) or `:every_iteration`
* `:inject_limit` - Max entries to inject (default: 3)
* `:inject_min_score` - Minimum score for injection (default: 0.3)
**Optional — compilation:**
* `:compiler_model` - Model string for compilation LLM calls
* `:auto_compile` - Auto-compile pending documents after runs (default: false)
"""
@behaviour Nous.Plugin
require Logger
alias Nous.KnowledgeBase.Tools
@impl true
def init(_agent, ctx) do
config = ctx.deps[:kb_config] || %{}
store_mod = config[:store]
unless store_mod do
Logger.warning(
"Nous.Plugins.KnowledgeBase: No :store configured in deps[:kb_config]. " <>
"Knowledge base tools will not function."
)
ctx
else
store_opts = Map.get(config, :store_opts, [])
store_opts = if is_map(store_opts), do: Map.to_list(store_opts), else: store_opts
case store_mod.init(store_opts) do
{:ok, store_state} ->
updated_config =
config
|> Map.put(:store_state, store_state)
|> Map.put_new(:auto_inject, true)
|> Map.put_new(:inject_strategy, :first_only)
|> Map.put_new(:inject_limit, 3)
|> Map.put_new(:inject_min_score, 0.3)
|> Map.put_new(:auto_compile, false)
|> Map.put(:_inject_done, false)
%{ctx | deps: Map.put(ctx.deps, :kb_config, updated_config)}
{:error, reason} ->
Logger.error("Nous.Plugins.KnowledgeBase: Store init failed: #{inspect(reason)}")
ctx
end
end
end
@impl true
def tools(_agent, _ctx) do
Tools.all_tools()
end
@impl true
def system_prompt(_agent, ctx) do
config = ctx.deps[:kb_config] || %{}
if config[:store_state] do
"""
## Knowledge Base
You have access to a curated knowledge base wiki. Use these tools:
- `kb_search` — Search the wiki for relevant entries
- `kb_read` — Read a specific entry by slug or ID
- `kb_ingest` — Add a raw document for LLM compilation
- `kb_add_entry` — Directly create or update a wiki entry
- `kb_link` — Create a link between entries
- `kb_backlinks` — Find entries linking to a given entry
- `kb_list` — List entries filtered by tag/concept/type
- `kb_health_check` — Audit the wiki for issues
- `kb_generate` — Generate a report or summary from entries
When answering questions, search the knowledge base first. Use [[slug]] format \
for wiki-links between entries. Cite which entries you used in your answer.\
"""
end
end
@impl true
def before_request(_agent, ctx, tools) do
config = ctx.deps[:kb_config] || %{}
should_inject? =
config[:auto_inject] == true &&
config[:store_state] != nil &&
should_inject_this_iteration?(config)
if should_inject? do
ctx = inject_relevant_entries(ctx, config)
updated_config = Map.put(config, :_inject_done, true)
ctx = %{ctx | deps: Map.put(ctx.deps, :kb_config, updated_config)}
{ctx, tools}
else
{ctx, tools}
end
end
# ---------------------------------------------------------------------------
# Private
# ---------------------------------------------------------------------------
defp should_inject_this_iteration?(config) do
case config[:inject_strategy] do
:every_iteration -> true
_ -> config[:_inject_done] != true
end
end
defp inject_relevant_entries(ctx, config) do
query = latest_user_query(ctx.messages)
if query do
store_mod = config[:store]
store_state = config[:store_state]
limit = config[:inject_limit] || 3
min_score = config[:inject_min_score] || 0.3
search_opts = [
limit: limit,
min_score: min_score,
kb_id: config[:kb_id]
]
case store_mod.search_entries(store_state, query, search_opts) do
{:ok, []} ->
ctx
{:ok, results} ->
kb_text =
results
|> Enum.map(fn {entry, score} ->
summary = entry.summary || String.slice(entry.content, 0, 200)
"- [[#{entry.slug}]] #{entry.title} (score: #{Float.round(score, 3)}): #{summary}"
end)
|> Enum.join("\n")
kb_msg = Nous.Message.system("[Relevant Knowledge]\n#{kb_text}")
%{ctx | messages: ctx.messages ++ [kb_msg]}
_ ->
ctx
end
else
ctx
end
end
defp latest_user_query(messages) do
messages
|> Enum.reverse()
|> Enum.find_value(fn
%{role: :user, content: content} when is_binary(content) -> content
_ -> nil
end)
end
end