Tutorial: Research Agent
Build an agent that takes a research question, searches the web, reads PDF sources, and produces a cited summary.
Prerequisites
- Installation
BRAVE_API_KEYorTAVILY_API_KEY(web search)ANTHROPIC_API_KEYorOPENAI_API_KEY(LLM)
1. Define the role
package com.example.tnsai.docs;
import com.tnsai.models.role.RoleIdentity;
import com.tnsai.roles.Role;
public final class ResearchRole extends Role {
@Override
public RoleIdentity getIdentity() {
return new RoleIdentity(
"researcher",
"Find sources and produce cited summaries",
"research");
}
}2. Build the agent
package com.example.tnsai.docs;
import com.tnsai.accountability.AuthorityScope;
import com.tnsai.accountability.RecordingLiabilitySink;
import com.tnsai.agents.Agent;
import com.tnsai.agents.AgentBuilder;
import com.tnsai.enums.BuiltInTool;
import com.tnsai.identity.AgentDescriptor;
import com.tnsai.identity.LocalIdentityProvider;
import com.tnsai.llm.providers.AnthropicClient;
import com.tnsai.roles.Role;
import java.time.Duration;
import java.util.List;
public final class ResearchAgentExample {
private ResearchAgentExample() {
}
public static Agent buildAgent() {
String model = "claude-sonnet-4-20250514";
List<String> toolNames = List.of(
"duckduckgo", "wikipedia", "wikidata", "searxng", "npm",
"maven_central", "brave_search", "serpapi", "tavily", "exa",
"pdf_extract_text", "pdf_extract_pages", "pdf_metadata",
"pdf_merge", "pdf_to_image", "markitdown");
var principal = new LocalIdentityProvider().issue(
AgentDescriptor.builder()
.agentClass(ResearchRole.class.getName())
.systemPrompt("Find authoritative sources and cite every factual claim.")
.toolNames(toolNames)
.model("anthropic:" + model)
.build());
return AgentBuilder.create()
.role(Role.create(ResearchRole.class))
.llm(new AnthropicClient(model))
.builtInTools(
BuiltInTool.WEB_SEARCH_TOOLS,
BuiltInTool.PDF_TOOLS,
BuiltInTool.MARKDOWN_TOOLS)
.principal(principal)
.liabilitySink(new RecordingLiabilitySink())
.authorityScope(AuthorityScope.unrestricted(Duration.ofHours(1)))
.build();
}
public static void main(String[] args) {
Agent agent = buildAgent();
agent.start();
}
}AgentBuilder.build() requires the accountability trio shown above. Replace
RecordingLiabilitySink with a durable sink in production, and narrow the
authority scope to the task rather than using unrestricted(...).
Each BuiltInTool enum constant registers every @Tool method on the
backing POJO in tnsai-tools — see the
built-in tool catalog for the full
per-toolkit method list. The LLM picks one method per call (e.g.
brave_search, then pdf_extract_text).
3. Stream the response
agent.streamChatWithTools(
"What are the latest results on RAG hallucination mitigation?",
chunk -> {
if (chunk.isContent()) System.out.print(chunk.getContent());
}
);4. Validate citations (optional)
Wrap the agent call in an Evaluator that checks the response contains at least one URL or DOI per factual claim. Fail the eval if citation density falls below the threshold.
Related
- Agents: Fundamentals
- Tools: Catalog
- Capabilities: RAG — full RAG pipeline if you want to embed corpora rather than search the web
Tutorial: Reusable Capabilities
Build an editorial agent that can summarise, translate, and classify sentiment — without writing a single body for those action methods. This tutorial walks through the @Capability pattern, showing composition, override, and the common mistakes the framework prevents.
Tutorial: Declarative Action RAG
Use @KnowledgeSource to declare a corpus and @Retrieval to ground a specific action. TnsAI loads the corpus lazily, retrieves matching documents before the action executor runs, and adds the formatted context to the request sent by an ActionType.LLM action.