Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

Agentic AI with Spring Boot + Java 17 + Gemini

A minimal but complete example of an agent: a service that doesn't just answer from the LLM's own knowledge, but can decide to call Java "tools" (functions), look at the result, and keep going until it has a real answer.

How it works

User message
     │
     ▼
AgentService.chat()  ──────────────►  GeminiClient.generateContent()
     ▲                                        │
     │                                 Gemini decides:
     │                          "I need to call a tool" OR "here's my answer"
     │                                        │
     │              ┌─────────────────────────┴─────────────────────────┐
     │              ▼                                                   ▼
     │      functionCall part                                    plain text part
     │              │                                                   │
     │      ToolRegistry.execute()                                      │
     │      (runs real Java code,                                       │
     │       e.g. CalculatorTool)                                       │
     │              │                                                   │
     │      result appended to conversation                             │
     └──────────────┘                                                   │
        loop back to Gemini                                     return to caller

This request/response/tool-call loop is what makes it "agentic" rather than a plain chatbot: the model is given autonomy to choose if, which, and how many tools to call before answering, and can chain several calls in a row.

Project layout

src/main/java/com/example/agent/
├── AgentApplication.java          Spring Boot entry point
├── config/
│   ├── GeminiProperties.java      binds gemini.* from application.yml
│   └── AppConfig.java             RestClient bean
├── service/
│   ├── GeminiClient.java          raw HTTP call to Gemini's generateContent endpoint
│   └── tools/
│       ├── AgentTool.java         interface every tool implements
│       ├── ToolRegistry.java      auto-discovers all AgentTool beans
│       └── (16 tools, see catalog below)
├── agent/
│   └── AgentService.java          the agent loop itself
└── controller/
    └── AgentController.java       REST endpoint: POST /api/agent/chat

Full tool catalog (16 tools)

Tool What it does
calculator Exact arithmetic (add/subtract/multiply/divide)
get_weather Weather lookup (mock data — swap for a real API)
http_get Fetches a URL over HTTP(S), returns the response body
get_current_time Current date/time in any IANA timezone
unit_convert Converts length, weight, or temperature between units
uuid_generate Generates one or more random UUIDs
hash_text MD5 / SHA-1 / SHA-256 / SHA-512 of a string
base64_encode / base64_decode Base64 conversion
json_format Validates JSON and pretty-prints it
word_count Line/word/character counts for a block of text
string_replace Literal or regex find-and-replace
regex_extract Pulls matches (and capture groups) out of text
random_number Random integer in a given range
read_file / write_file Read/write local files (unrestricted — see security note below)

Adding a new tool

You don't touch the agent loop at all. Just add a new @Component implementing AgentTool:

@Component
public class MyTool implements AgentTool {
    public String getName() { return "my_tool"; }
    public String getDescription() { return "Explain what it does and WHEN to use it"; }
    public Map<String,Object> getParametersSchema() { /* JSON schema of args */ }
    public String execute(JsonNode args) { /* do the work, return a string */ }
}

ToolRegistry picks it up automatically via Spring's dependency injection (List<AgentTool> constructor injection), and it will show up in the next tools payload sent to Gemini.

Running it

  1. Get a free Gemini API key: https://aistudio.google.com/app/apikey
  2. Set it as an environment variable:
    export GEMINI_API_KEY=your_key_here
  3. Build & run (requires Java 17 and Maven):
    mvn spring-boot:run
  4. Call the agent:
    curl -X POST http://localhost:8080/api/agent/chat \
      -H "Content-Type: application/json" \
      -d '{"message":"What is 234 * 18, and what is the weather in Delhi?"}'
    Watch the DEBUG logs — you'll see Gemini call calculator, then get_weather, then produce a final combined answer, all from a single user message.

Notes / things to change for production

  • Conversation state is a single in-memory List for demo simplicity. In a real app, key it per user/session (e.g. Map<String, List<...>> or store in Redis).
  • Model name: gemini-3.6-flash is set in application.yml; check https://ai.google.dev/gemini-api/docs/models for the current model list, since Google renames/deprecates models over time.
  • Tool-result role: when sending a functionResponse back to the model, use role: "user". Newer Gemini models (the gemini-3.x family) reject role: "function" with a 400 error — that role was used by some older models/SDKs but is no longer accepted. AgentService.java is already set up correctly for this.
  • thoughtSignature: gemini-3.x models can attach a thoughtSignature to functionCall parts, which the model uses to keep track of its own reasoning across a multi-step tool-calling turn. This project already forwards the entire functionCall part verbatim back into the conversation (see AgentService.chat()), so any thoughtSignature present is preserved automatically — you don't need to do anything extra unless you start transforming the raw JSON instead of passing it through as-is.
  • Error handling / retries around the HTTP call to Gemini are intentionally left minimal — add @Retryable or a circuit breaker (resilience4j) for production.
  • WeatherTool returns mock data — swap the body for a real HTTP call via the same RestClient pattern used in GeminiClient.
  • read_file / write_file / http_get touch the filesystem and network with no restriction — fine for local experimentation, but before exposing this agent beyond your own machine: allow-list a base directory for file access, and block requests to localhost/private IP ranges for http_get to prevent SSRF.
  • Consider streaming (generateContent with alt=sse / streamGenerateContent) if you want token-by-token output instead of waiting for the full response.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages