Packages
nous
0.17.0
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/knowledge_base/entry.ex
defmodule Nous.KnowledgeBase.Entry do
@moduledoc """
A compiled wiki entry — the core unit of the knowledge base.
Entries are produced by LLM compilation of raw `Document`s. They contain
structured markdown with `[[wiki-links]]`, summaries, extracted concepts,
and metadata for search and graph traversal.
"""
alias __MODULE__
@type entry_type :: :article | :concept | :summary | :index | :glossary
@type t :: %Entry{
id: String.t(),
title: String.t(),
slug: String.t(),
content: String.t(),
title_down: String.t() | nil,
content_down: String.t() | nil,
summary: String.t() | nil,
entry_type: entry_type(),
concepts: [String.t()],
tags: [String.t()],
confidence: float(),
source_doc_ids: [String.t()],
embedding: [float()] | nil,
metadata: map(),
kb_id: String.t() | nil,
created_at: DateTime.t(),
updated_at: DateTime.t(),
last_verified_at: DateTime.t() | nil
}
defstruct [
:id,
:title,
:slug,
:content,
:title_down,
:content_down,
:summary,
:embedding,
:kb_id,
:last_verified_at,
entry_type: :article,
concepts: [],
tags: [],
confidence: 0.5,
source_doc_ids: [],
metadata: %{},
created_at: nil,
updated_at: nil
]
@doc """
Creates a new Entry from attributes.
Requires `:title` and `:content`. Auto-generates id, slug, and timestamps.
"""
def new(attrs) when is_map(attrs) do
now = DateTime.utc_now()
id = Map.get(attrs, :id) || generate_id()
title = Map.fetch!(attrs, :title)
content = Map.fetch!(attrs, :content)
%Entry{
id: id,
title: title,
slug: Map.get(attrs, :slug) || slugify(title),
content: content,
title_down: String.downcase(title),
content_down: String.downcase(content),
summary: Map.get(attrs, :summary),
entry_type: Map.get(attrs, :entry_type, :article),
concepts: Map.get(attrs, :concepts, []),
tags: Map.get(attrs, :tags, []),
confidence: Map.get(attrs, :confidence, 0.5),
source_doc_ids: Map.get(attrs, :source_doc_ids, []),
embedding: Map.get(attrs, :embedding),
metadata: Map.get(attrs, :metadata, %{}),
kb_id: Map.get(attrs, :kb_id),
created_at: Map.get(attrs, :created_at, now),
updated_at: Map.get(attrs, :updated_at, now),
last_verified_at: Map.get(attrs, :last_verified_at)
}
end
@doc """
Recomputes the cached downcased search fields from `title`/`content`.
Call after any mutation of `title` or `content` (e.g. store `update_entry`)
so search never scores against a stale cache.
"""
def with_downcase_cache(%Entry{} = entry) do
%{
entry
| title_down: String.downcase(entry.title),
content_down: String.downcase(entry.content)
}
end
@doc """
Generates a URL-safe slug from a title.
## Examples
iex> Nous.KnowledgeBase.Entry.slugify("Elixir GenServer Patterns")
"elixir-genserver-patterns"
"""
def slugify(title) when is_binary(title) do
# L-3: normalise unicode to NFD and strip combining marks so accented
# characters are preserved as their base ASCII form ("Café" -> "cafe")
# rather than entirely stripped (the previous \w-only filter dropped
# them). Multilingual titles still collide on slug; the Store layer
# is responsible for slug uniqueness (see Store moduledoc).
title
|> :unicode.characters_to_nfd_binary()
|> String.replace(~r/[\x{0300}-\x{036F}]/u, "")
|> String.downcase()
|> String.replace(~r/[^a-z0-9_\s-]/u, "")
|> String.replace(~r/[\s_]+/, "-")
|> String.replace(~r/-+/, "-")
|> String.trim("-")
end
defp generate_id do
:crypto.strong_rand_bytes(16) |> Base.encode16(case: :lower)
end
end