# Step 01 - Implementing AI Agents

## Welcome to Section 2: Agentic Systems

Congratulations on completing Section 1! You've learned how to build AI-infused applications with chatbots, RAG patterns, and function calling.

In **Section 2**, we're shifting gears to explore **agentic systems** — autonomous AI agents that can work together to solve complex, multi-step problems. Instead of a chatbot responding to user queries, you'll build agents that can make decisions, use tools, and collaborate in workflows.

### What You'll Learn

In this section, you will:

- Understand the difference between **AI Services** (Section 1) and **AI Agents** (Section 2)
- Build your first autonomous agent using the `quarkus-langchain4j-agentic` module
- Learn how agents use tools (_function calling_) to take actions
- See agents make decisions based on contextual information

---

## A New Scenario: Car Management System

The **Miles of Smiles** car rental company needs help managing their fleet. 
Here's the business process flow:

1. **Rental Returns**: When customers return cars, the rental team records feedback about the car's condition.
2. **Cleaning Requests**: Based on the feedback, the system should automatically decide if the car needs cleaning.
3. **Cleaning Returns**: After cleaning, the team provides their own feedback and returns the car.
4. **Fleet Availability**: Clean cars with no issues return to the available pool for rental.

Your job is to build an **AI agent** that can analyze feedback and _intelligently_ decide whether to request a cleaning.

---

## AI Services vs. AI Agents

Before diving in, let's clarify some key differences:

| Feature         | AI Services (Section 1)                         | AI Agents (Section 2)                                                                                                                                                                                                            |
|-----------------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Purpose**     | Answer user questions                           | Perform autonomous tasks                                                                                                                                                                                                         |
| **Interaction** | Reactive (responds to prompts)                  | Reactive and Proactive (takes actions)                                                                                                                                                                                           |
| **Tool Usage**  | Can call tools when needed                      | Can call tools to accomplish goals                                                                                                                                                                                               |
| **Workflows**   | Single-agent interactions                       | Multi-agent collaboration  ([workflow](https://docs.langchain4j.dev/tutorials/agents/#workflow-patterns){target="_blank"} or [supervisor-based](https://docs.langchain4j.dev/tutorials/agents/#pure-agentic-ai){target="_blank"}) |
| **Annotation**  | Methods use `@SystemMessage` and `@UserMessage` | One method per interface (using `@Agent`)                                                                                                                                                                                        |
| **Use Cases**   | Chatbots, Q&A, content generation               | Automation, decision-making, orchestration                                                                                                                                                                                       |

In this section, you'll see how agents extend the capabilities you created in Section 1 to build sophisticated, intelligent, and autonomous systems.

---

## Prerequisites

Before starting, ensure you have met the [workshop setup requirements](../requirements.md){target="_blank"}.

---

## Running the Application

Navigate to the `section-2/step-01` directory and start the application:

=== "Linux / macOS"
    ```bash
    cd section-2/step-01
    ./mvnw quarkus:dev
    ```

=== "Windows"
    ```cmd
    cd section-2\step-01
    mvnw quarkus:dev
    ```

Once started, open your browser to [http://localhost:8080](http://localhost:8080){target="_blank"}.

### Understanding the UI

The application has two main sections:

1. **Fleet Status** (top): Shows all cars in the Miles of Smiles fleet with their current status.
2. **Action** column: For rented or cleaning cars, provides an inline feedback form to process returns.

![Agentic App UI](../images/agentic-UI-1.png){: .center}

---

## Try It Out

Let's see the agent in action!

### Test 1: Car Needs Cleaning

Act as a rental team member processing a car return. In the **Fleet Status** grid, find a rented car and enter this feedback in its **Action** column:

```
Car has dog hair all over the back seat
```

Click the **Return** button.

**What happens?**

- The agent analyzes the feedback
- Recognizes the car needs cleaning
- Calls the `CleaningTool` to request interior cleaning
- Updates the car's status to `AT_CLEANING`

Check your terminal logs (you may need to scroll up). You should see output like:

```
🚗 CleaningTool result: Cleaning requested for Mercedes-Benz C-Class (2020), Car #6:
- Interior cleaning
Additional notes: Interior cleaning required due to dog hair in back seat.
```

### Test 2: Car Is Clean

Now try returning a car that's already clean:

```
Car looks good
```

**What happens?**

- The agent analyzes the feedback
- Determines no cleaning is needed
- Returns `CLEANING_NOT_REQUIRED` (no tool call made)
- Updates the car's status to `AVAILABLE`

In your logs, you'll see the agent's response contains:

```
"content": "CLEANING_NOT_REQUIRED"
```

Notice how the agent **made a decision** without calling the cleaning tool. This demonstrates reasoning!

---

## Building Agents with Quarkus LangChain4j

The [langchain4j-agentic](https://docs.langchain4j.dev/tutorials/agents){target="_blank"} module introduces the ability to create AI Agents.
This module is available in Quarkus using the `quarkus-langchain4j-agentic` module.
If you open the `pom.xml` file from the project, you will see this dependency:

```xml
<dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-agentic</artifactId>
</dependency>
```

### Key Concepts

Agents share similarities with AI Services from [Section 1](../section-1/step-01.md){target="_blank"}:

- Declared as interfaces (implementation generated automatically)
- Use `@SystemMessage` to define the agent's role and behavior
- Use `@UserMessage` to provide request-specific context
- Can be assigned **tools** to perform actions
- Support both programmatic and declarative (annotation-based) definitions, even if in Quarkus, we recommend the declarative approach

### Key Differences

- **Only one method** per interface can be annotated with `@Agent` - this is the agent entry point
- Designed to be composed with **workflows** or be invoked by a supervisor — agents can be composed together (more on this in [Step 02](step-02.md){target="_blank"})
- Focus on **autonomous actions** rather than conversational responses

---

## Understanding the Application Architecture

![App Blueprint](../images/agentic-app-1.png){: .center}

The application consists of four main components:

1. **CarManagementResource**: REST API endpoints
2. **CarManagementService**: Business logic and agent orchestration
3. **CleaningAgent**: AI agent that decides if cleaning is needed
4. **CleaningTool**: Tool that requests cleaning services

Let's explore each component.

---

## Component 1: REST API Endpoints

The `CarManagementResource` provides REST APIs to handle car returns:

```java hl_lines="19 22" title="CarManagementResource.java"
--8<-- "../../section-2/step-01/src/main/java/com/carmanagement/resource/CarManagementResource.java:car-management"
```

**Key Points:**

- The `processReturn` method (endpoint `/car-management/return/{carNumber}`): Accepts feedback and routes it based on the car's current status
- Delegates to `CarManagementService.processCarReturn`

---

## Component 2: Business Logic & Agent Invocation

The `CarManagementService` orchestrates the car return process:

```java hl_lines="1-2 13 19-24" title="CarManagementService.java"
--8<-- "../../section-2/step-01/src/main/java/com/carmanagement/service/CarManagementService.java:processCarReturn"
```

**Key Points:**

- The `CleaningAgent` field is injected as a CDI bean
- In the `processCarReturn` method, the agent is invoked with car details and feedback. The response is checked for `CLEANING_NOT_REQUIRED`:
    * If found → Car marked as `AVAILABLE`
    * If not found → Car stays `AT_CLEANING` (tool was called)

This simple pattern allows you to ***integrate autonomous decision-making into your business logic***!

---

## Component 3: The CleaningAgent

Here's where the magic happens — the AI agent definition:

```java hl_lines="6-11 21-22" title="CleaningAgent.java"
--8<-- "../../section-2/step-01/src/main/java/com/carmanagement/agentic/agents/CleaningAgent.java:cleaningAgent"
```

**Let's break it down:**

### Using Context Objects for Cleaner Parameters

Notice how the agent method signature uses `CarInfo` for car details and a simple `String feedback` for the user's feedback:

```java
String processCleaning(CarInfo carInfo, Integer carNumber, String feedback)
```

This approach groups related car parameters into a cohesive `CarInfo` object, keeping the method signature clean and maintainable. The feedback is passed as a simple string — specialized agents will evaluate this string differently based on their role (e.g. a cleaning agent will look for clues that the car needs to be cleaned, while a maintenance agent will look for any signs that point to the car needing repairs).

**Benefits of this pattern:**

- **Reduced parameter count**: Instead of passing 6-8 individual parameters, we pass a few meaningful objects
- **Better organization**: Related car data stays together in `CarInfo`
- **Simplicity**: Feedback flows through the system as a plain string
- **Type safety**: The compiler ensures you pass the right context objects

This pattern becomes even more valuable as workflows grow more complex in later steps.

### `@SystemMessage`
Defines the agent's **role** and **decision-making logic**:

- Acts as the intake specialist for the cleaning department
- Should call the `requestCleaning` function in the `CleaningTool` when cleaning is needed
- Should be specific about which services to request
- Should return `CLEANING_NOT_REQUIRED` if no cleaning is needed

!!! tip "Pro Tip: Clear Instructions Matter"
    The system message is critical! It tells the agent:

    - **WHO** it is (cleaning intake specialist)
    - **WHAT** to do (submit cleaning requests)
    - **WHEN** to act (based on feedback)
    - **HOW** to respond (specific services or `CLEANING_NOT_REQUIRED`)

### `@UserMessage` 
Provides **context** for each request using template variables:

- Car details: `{carMake}`, `{carModel}`, `{carYear}`, `{carNumber}`
- Feedback sources: `{rentalFeedback}`, `{cleaningFeedback}`

These variables are automatically populated from the method parameters.

### `@Agent`
Marks this as an **agent method** — only one per interface.

- Provides a description: "Cleaning specialist. Determines what cleaning services are needed."
- This description can be used by other agents or systems to understand this agent's purpose

### `@ToolBox`
Assigns the `CleaningTool` to this agent:

- The agent can call methods in this tool to perform actions
- The LLM decides when and how to use the tool based on the task (function calling has been covered in Section 1 of the workshop)

### Method Signature
Defines the inputs and output:

- **Inputs**: All the context the agent needs to make decisions
- **Output**: `String` — the agent's response (either tool result or `CLEANING_NOT_REQUIRED`)

!!! info "No Implementation Required"
    Notice there's **no method body**! LangChain4j automatically generates the implementation:

    1. Receives the inputs
    2. Sends the system + user messages to the LLM
    3. If the LLM wants to call the tool, it does so
    4. Returns the final response

---

## Component 4: The CleaningTool

If you went through Section 1, you'll remember that we already covered [tool and function calling](../section-1/step-07.md){target="_blank"} for single AI services.
They work in pretty much the exact same way for Agents: Tools enable agents to call functions that can take action.
These tools can be local LangChain4j tools running within the same Quarkus application, like in the following `CleaningTool` example, or remote, using the [MCP protocol we visited in Section 1, Step 8](../section-1/step-08.md){target="_blank"}..

```java hl_lines="4 21 40 47-48" title="CleaningTool.java"
--8<-- "../../section-2/step-01/src/main/java/com/carmanagement/agentic/tools/CleaningTool.java:CleaningTool"
```

**Key Points:**

- `@Tool` annotation exposes this method to agents
    - The description helps the LLM understand when to use this tool
    - Parameters define what information the agent must provide
- The method updates the car status to `AT_CLEANING`, if the `carInfo` is not `null`
- The method returns a summary of the request (and prints a log messages)

### Understanding Tool Execution Flow

Here is the sequence of actions happening when the agent is invoked:

1. Agent receives car return feedback (entered by the user)
2. LLM analyzes the feedback
3. LLM decides to call `requestCleaning` (or not, depending on the feedback)
4. If called, LLM determines which parameters to use:
     - Should `interiorCleaning` be true?
     - Should `exteriorWash` be true?
     - What `requestText` should be included?
5. Tool executes and returns a result
6. Agent receives the result and can respond

## How It All Works Together

Let's trace through a complete example:

### Scenario: Dog Hair in Back Seat

```mermaid
sequenceDiagram
    participant User
    participant REST as CarManagementResource
    participant Service as CarManagementService
    participant Agent as CleaningAgent
    participant LLM as OpenAI LLM
    participant Tool as CleaningTool

    User->>REST: POST /return/6<br/>feedback: "Dog hair in back seat"
    REST->>Service: processCarReturn(6, "Dog hair...", "")
    Service->>Agent: processCleaning(...)
    Agent->>LLM: System: You handle cleaning intake...<br/>User: Car #6, feedback: "Dog hair..."
    LLM->>LLM: Analyze feedback<br/>Decision: Needs interior cleaning
    LLM->>Tool: requestCleaning(<br/>  carNumber: 6,<br/>  interiorCleaning: true,<br/>  requestText: "Dog hair removal"<br/>)
    Tool->>Tool: Update car status to AT_CLEANING
    Tool-->>LLM: "Cleaning requested: Interior cleaning..."
    LLM-->>Agent: "Cleaning requested: Interior cleaning..."
    Agent-->>Service: "Cleaning requested: Interior cleaning..."
    Service->>Service: Check if contains "CLEANING_NOT_REQUIRED"<br/>No → Keep status AT_CLEANING
    Service-->>REST: Result message
    REST-->>User: 200 OK
```

### Scenario: Car Looks Good

```mermaid
sequenceDiagram
    participant User
    participant REST as CarManagementResource
    participant Service as CarManagementService
    participant Agent as CleaningAgent
    participant LLM as OpenAI LLM

    User->>REST: POST /return/3<br/>feedback: "Car looks good"
    REST->>Service: processCarReturn(3, "Car looks good", "")
    Service->>Agent: processCleaning(...)
    Agent->>LLM: System: You handle cleaning intake...<br/>User: Car #3, feedback: "Car looks good"
    LLM->>LLM: Analyze feedback<br/>Decision: No cleaning needed
    LLM-->>Agent: "CLEANING_NOT_REQUIRED"
    Agent-->>Service: "CLEANING_NOT_REQUIRED"
    Service->>Service: Check if contains "CLEANING_NOT_REQUIRED"<br/>Yes → Set status to AVAILABLE
    Service-->>REST: Result message
    REST-->>User: 200 OK
```

---

## Key Takeaways

- **Agents are autonomous**: They make decisions and take actions based on context.
- **Tools enable actions**: Agents use tools to interact with systems (databases, APIs, etc.)
- **Clear prompts matter**: The `@SystemMessage` guides the agent's decision-making
- **Type-safe interfaces**: No manual API calls — just define interfaces and let Quarkus LangChain4j handle the rest
- **CDI integration**: Agents and tools are managed beans that integrate seamlessly with Quarkus

---

## Experiment Further

Try these experiments to deepen your understanding:

### 1. Test Edge Cases

Try different feedback scenarios:

```
The trunk smells like fish
```

```
Minor scratch on the bumper
```

```
Spotless condition
```

What does the agent decide for each? Does it call the cleaning tool?

### 2. Modify the System Message

Edit `CleaningAgent.java` and change the system message. For example:

```java
@SystemMessage("""
    You are a very picky cleaning intake specialist.
    Request a full detail (exterior, interior, waxing, detailing)
    unless the car is absolutely perfect.
    If perfect, respond with "CLEANING_NOT_REQUIRED".
    """)
```

How does this change the agent's behavior?

### 3. Add More Tool Parameters

Edit `CleaningTool.java` to add a `tireCleaning` parameter. 
Does the agent automatically learn to use it?

---

## Troubleshooting

??? warning "Error: OPENAI_API_KEY not set"
    Make sure you've exported the environment variable:

    ```bash
    export OPENAI_API_KEY=sk-your-key-here
    ```

    Then restart the application.

??? warning "Tool methods not being called"
   - Verify the tool uses `@Dependent` scope
   - Check that the `@Tool` annotation is present
   - Ensure the tool is properly referenced in `@ToolBox`

??? warning "Agent always/never calls the tool"
   - Review your `@SystemMessage` — is it clear about when to use the tool?
   - Try adding more explicit instructions
   - Consider providing examples in the system message

---
## Cleanup

Before moving to the next step, let's clean up:

1. **Stop the running server** by pressing `Ctrl+C` in the terminal where Quarkus is running

2. **Return to the root project directory**:

    ```bash
    cd ..
    ```

---


## What's Next?

In this step, you built a **single autonomous agent** that makes decisions and uses tools.

In **Step 02**, you'll learn how to compose **multiple agents into workflows** — where agents collaborate to solve complex problems together!

[Continue to Step 02 - Composing Simple Agentic Workflows](step-02.md)