Skip to content
tnsaijava agent framework

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 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.