Packages
nous
0.14.2
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/tools/research_notes.ex
defmodule Nous.Tools.ResearchNotes do
@moduledoc """
Structured note-taking tools for research agents.
Provides tools to track findings, gaps, and contradictions across
research iterations. Uses ContextUpdate to persist state in the
agent's context deps.
## Usage
tools = Nous.Tools.ResearchNotes.all_tools()
agent = Agent.new("openai:gpt-4", tools: tools)
"""
alias Nous.Tool
alias Nous.Tool.ContextUpdate
@doc """
Returns all research note tools as a list.
"""
def all_tools do
[
add_finding_tool(),
list_findings_tool(),
add_gap_tool(),
list_gaps_tool(),
add_contradiction_tool()
]
end
# Tool definitions
defp add_finding_tool do
%Tool{
name: "add_finding",
description:
"Record a research finding with source and confidence score. Use this to track facts discovered during research.",
parameters: %{
"type" => "object",
"properties" => %{
"claim" => %{"type" => "string", "description" => "The factual claim or finding"},
"source_url" => %{"type" => "string", "description" => "URL of the source"},
"source_title" => %{
"type" => "string",
"description" => "Title of the source article/page"
},
"confidence" => %{
"type" => "number",
"description" => "Confidence score 0.0-1.0 (default: 0.7)"
}
},
"required" => ["claim"]
},
function: &__MODULE__.add_finding/2,
takes_ctx: true
}
end
defp list_findings_tool do
%Tool{
name: "list_findings",
description: "List all recorded research findings so far.",
parameters: %{"type" => "object", "properties" => %{}, "required" => []},
function: &__MODULE__.list_findings/2,
takes_ctx: true
}
end
defp add_gap_tool do
%Tool{
name: "add_gap",
description:
"Record a knowledge gap - a question that still needs to be answered in the research.",
parameters: %{
"type" => "object",
"properties" => %{
"question" => %{
"type" => "string",
"description" => "The unanswered question or knowledge gap"
}
},
"required" => ["question"]
},
function: &__MODULE__.add_gap/2,
takes_ctx: true
}
end
defp list_gaps_tool do
%Tool{
name: "list_gaps",
description: "List all remaining knowledge gaps in the research.",
parameters: %{"type" => "object", "properties" => %{}, "required" => []},
function: &__MODULE__.list_gaps/2,
takes_ctx: true
}
end
defp add_contradiction_tool do
%Tool{
name: "add_contradiction",
description: "Record a contradiction found between two sources or claims.",
parameters: %{
"type" => "object",
"properties" => %{
"claim_a" => %{"type" => "string", "description" => "First conflicting claim"},
"claim_b" => %{"type" => "string", "description" => "Second conflicting claim"},
"sources" => %{
"type" => "string",
"description" => "Sources for each claim (comma-separated URLs)"
}
},
"required" => ["claim_a", "claim_b"]
},
function: &__MODULE__.add_contradiction/2,
takes_ctx: true
}
end
# Tool implementations
def add_finding(ctx, args) do
finding = %{
claim: Map.fetch!(args, "claim"),
source_url: Map.get(args, "source_url"),
source_title: Map.get(args, "source_title"),
confidence: Map.get(args, "confidence", 0.7),
recorded_at: DateTime.utc_now() |> DateTime.to_iso8601()
}
existing = ctx.deps[:research_findings] || []
# Deduplicate by checking similar claims
is_duplicate =
Enum.any?(existing, fn f ->
String.jaro_distance(f.claim, finding.claim) > 0.85
end)
if is_duplicate do
{:ok, %{status: "duplicate", message: "A similar finding already exists"},
ContextUpdate.new()}
else
{:ok, %{status: "recorded", finding: finding, total_findings: length(existing) + 1},
ContextUpdate.new() |> ContextUpdate.append(:research_findings, finding)}
end
end
def list_findings(ctx, _args) do
findings = ctx.deps[:research_findings] || []
%{
findings: findings,
count: length(findings),
high_confidence: Enum.count(findings, &(&1.confidence >= 0.8)),
low_confidence: Enum.count(findings, &(&1.confidence < 0.5))
}
end
def add_gap(ctx, args) do
question = Map.fetch!(args, "question")
existing = ctx.deps[:research_gaps] || []
{:ok, %{status: "recorded", question: question, total_gaps: length(existing) + 1},
ContextUpdate.new() |> ContextUpdate.append(:research_gaps, question)}
end
def list_gaps(ctx, _args) do
gaps = ctx.deps[:research_gaps] || []
%{gaps: gaps, count: length(gaps)}
end
def add_contradiction(_ctx, args) do
contradiction = %{
claim_a: Map.fetch!(args, "claim_a"),
claim_b: Map.fetch!(args, "claim_b"),
sources: Map.get(args, "sources", ""),
recorded_at: DateTime.utc_now() |> DateTime.to_iso8601()
}
{:ok, %{status: "recorded", contradiction: contradiction},
ContextUpdate.new() |> ContextUpdate.append(:research_contradictions, contradiction)}
end
end