Skip to content
tnsaiDocs
GitHub

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_KEY or TAVILY_API_KEY (web search)
  • ANTHROPIC_API_KEY or OPENAI_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 principal and authorityScope shown above, and the liability sink is optional. 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());
    }
);