Skip to content

rkp4u/agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

β˜• Singapore Kopitiam Chatter

A multi-agent AI conversation system where four distinct Singapore kopitiam regulars chat amongst themselves β€” orchestrated by an LLM, evaluated by a judge, and persisted across sessions.

Built on Spring Boot + LangChain4j + LangGraph4j.


πŸ§‘β€πŸ€β€πŸ§‘ Meet the Regulars

Persona Age Background Speech Style Tools
Uncle Ah Seng 68 30+ years running the drinks stall. Pragmatic, thrifty, complains about costs. Heavy Singlish β€” lah, lor, wah time, weather
Mei Qi 21 Content creator promoting kopitiam online. Social media savvy, very chatty. Mix of English and Singlish β€” OMG, yasss, emojis time, news
Bala Nair 45 Ex-statistician turned football tipster. Sees patterns in everything. Dry humour. Formal English with occasional Singlish, statistical references time
Dr. Tan 72 Retired philosophy professor. Thoughtful, deep, sips his kopi-o slowly. Measured English with philosophical tangents (none)

πŸ—οΈ System Architecture

Graph Topology

START
  β”‚
  β–Ό
[human_node]          ← Seeds the opening message + sets volley count
  β”‚
  β–Ό
[orchestrator_node]   ← LLM picks who speaks next
  β”‚
  β”œβ”€ volley > 0 ──► [participant_node]  ← Selected persona responds (ReAct loop)
  β”‚                       β”‚
  β”‚                       └──────────────► [orchestrator_node]  (loops)
  β”‚
  └─ volley = 0 ──► [summarizer_node]   ← Generates conversation summary
                          β”‚
                          β–Ό
                   [evaluator_node]      ← LLM-as-Judge scores the conversation
                          β”‚
                         END

Node Responsibilities

Node Class What It Does
human_node inline in KopitiamGraph Seeds messages with the user's opening message and resets volley_msg_left
orchestrator_node OrchestratorNode Calls GPT-4o-mini to select the next speaker based on conversation history
participant_node ParticipantService Runs a ReAct loop β€” the selected persona may call tools before responding
summarizer_node SummarizerNode Generates a narrative summary of the full conversation
evaluator_node EvaluatorNode Scores the conversation on 3 dimensions and returns a JSON scorecard

Session Persistence (MemorySaver)

Every request carries an X-Session-Id header. The compiled graph is a Spring singleton with a MemorySaver checkpointer attached. Each session ID maps to its own checkpoint thread β€” so the same session ID on a follow-up request resumes the exact conversation state (message history, volley count, next speaker) from where it left off.

Request 1  X-Session-Id: abc  β†’  MemorySaver stores checkpoint["abc"]
Request 2  X-Session-Id: abc  β†’  MemorySaver restores checkpoint["abc"] β†’ conversation continues
Request 3  X-Session-Id: xyz  β†’  No checkpoint["xyz"] β†’ fresh conversation

Tools

Each persona has access to a subset of real-time tools, called via a simple TOOL:<name> protocol in the ReAct loop:

Tool Class Returns
time SingaporeTimeService Current Singapore time (SGT)
weather SingaporeWeatherService Current Singapore weather summary
news SingaporeNewsService Recent Singapore news headlines

πŸ› οΈ Tech Stack

Layer Technology Version
Language Java 21
Framework Spring Boot 3.4.1
Graph / Orchestration LangGraph4j 1.8.4
LLM Client LangChain4j 1.1.0 / 1.1.0-beta7
LLM Model OpenAI GPT-4o-mini β€”
Build Gradle Kotlin DSL β€”

πŸš€ Getting Started

Prerequisites

  • Java 21+
  • An OpenAI API key

Step 1 β€” Set your OpenAI API key

The application requires an OPENAI_API_KEY environment variable. Without it the app will not start.

export OPENAI_API_KEY=sk-your-key-here

You can also prefix it inline when running:

OPENAI_API_KEY=sk-your-key-here ./gradlew bootRun

Note: Never paste your key into application.yml directly. The file uses ${OPENAI_API_KEY} so it always reads from the environment.

Step 2 β€” Build the project

./gradlew build

Step 3 β€” Run the application

./gradlew bootRun

Or, if you want to pass the API key inline without exporting:

OPENAI_API_KEY=sk-your-key-here ./gradlew bootRun

The app starts on http://localhost:8080.

Step 4 β€” Start a conversation

curl http://localhost:8080/api/graph/invoke

The four regulars will chatter away. When done, you receive a JSON response:

{
  "status": "completed",
  "sessionId": "3f7a2b1c-49de-4a1b-bf23-9c1e7d8a0f22",
  "evaluation": {
    "character_consistency":     { "score": 5, "reason": "Each agent stayed firmly in character throughout." },
    "conversation_naturalness":  { "score": 4, "reason": "The banter flowed organically with good topic transitions." },
    "tool_usage_correctness":    { "score": 5, "reason": "Tools were only invoked when real-time data was genuinely needed." },
    "overall": 5,
    "summary": "A lively and authentic kopitiam conversation with strong character voices and natural flow."
  },
  "message": "Conversation ended successfully. Come back anytime lah!"
}

πŸ“‘ API Reference

GET /api/graph/invoke

Starts a new kopitiam conversation or resumes an existing session.

Parameter Type Where Required Default Description
X-Session-Id string Header No auto UUID Session identifier. Reuse to resume a previous conversation.
message string Query param No "Hello everyone! What's happening at the kopitiam today?" The opening message injected into the conversation.

Example β€” new session with default greeting:

curl http://localhost:8080/api/graph/invoke

Example β€” new session with a custom topic:

curl "http://localhost:8080/api/graph/invoke?message=What+do+you+all+think+about+the+new+MRT+line"

Example β€” resume a previous session:

curl -H "X-Session-Id: 3f7a2b1c-49de-4a1b-bf23-9c1e7d8a0f22" \
     "http://localhost:8080/api/graph/invoke?message=What+did+Ah+Seng+say+earlier"

Example β€” pin your own session ID:

curl -H "X-Session-Id: my-kopitiam-session" \
     http://localhost:8080/api/graph/invoke

πŸ“ Project Structure

src/main/java/org/example/
β”œβ”€β”€ Main.java
β”œβ”€β”€ config/
β”‚   └── PersonaRegistry.java        # Defines all 4 persona configs (name, background, tools)
β”œβ”€β”€ controller/
β”‚   β”œβ”€β”€ GraphController.java        # GET /api/graph/invoke β€” session + message routing
β”‚   └── PersonaController.java      # Persona inspection endpoints
β”œβ”€β”€ graph/
β”‚   β”œβ”€β”€ KopitiamGraph.java          # @Configuration β€” builds and exposes the compiled graph @Bean
β”‚   └── KopitiamState.java          # Shared state: messages (appender), volley_msg_left, next_speaker, evaluation
β”œβ”€β”€ model/
β”‚   └── Persona.java                # Persona data model
β”œβ”€β”€ nodes/
β”‚   β”œβ”€β”€ OrchestratorNode.java       # Speaker selection via LLM
β”‚   β”œβ”€β”€ SummarizerNode.java         # End-of-conversation summary
β”‚   └── EvaluatorNode.java          # LLM-as-Judge scoring node
β”œβ”€β”€ service/
β”‚   β”œβ”€β”€ GraphService.java           # Invokes the compiled graph with per-session RunnableConfig
β”‚   β”œβ”€β”€ OrchestratorService.java    # Speaker selection logic
β”‚   β”œβ”€β”€ ParticipantService.java     # ReAct loop β€” persona responds, optionally calling tools
β”‚   β”œβ”€β”€ SummarizerService.java      # Summary generation
β”‚   └── EvaluatorService.java       # Scores conversation on 3 dimensions, returns JSON scorecard
└── tools/
    β”œβ”€β”€ SingaporeNewsService.java    # Returns Singapore news headlines
    β”œβ”€β”€ SingaporeTimeService.java    # Returns current Singapore time (SGT)
    β”œβ”€β”€ SingaporeWeatherService.java # Returns Singapore weather
    └── ToolExecutor.java            # Routes TOOL:<name> calls to the right service

βš™οΈ Configuration

All settings live in src/main/resources/application.yml:

Key Default Description
langchain4j.open-ai.chat-model.model-name gpt-4o-mini OpenAI model used by all agents
langchain4j.open-ai.chat-model.temperature 0.7 Creativity level for agent responses
langchain4j.open-ai.chat-model.log-requests true Logs full HTTP request to OpenAI
langchain4j.open-ai.chat-model.log-responses true Logs full HTTP response from OpenAI
server.port 8080 Port the app listens on

Conversation length is controlled by DEFAULT_VOLLEYS in KopitiamGraph.java (default: 4 turns). Increase it for longer conversations.


πŸ§‘β€βš–οΈ Evaluation Scorecard

After every conversation the evaluator_node calls GPT-4o-mini with a structured judge prompt and scores the exchange on three dimensions:

Dimension What It Measures
character_consistency Did each agent stay true to their persona throughout?
conversation_naturalness Did the conversation flow organically, like real kopitiam banter?
tool_usage_correctness Were tools (time / weather / news) called only when genuinely needed?

Each dimension gets a score of 1–5 with a one-sentence reason. An overall score (1–5) and a one-sentence summary verdict are also returned. The scorecard is embedded directly in the API response as structured JSON.


πŸ’‘ Example Conversation Flow

You: Hello everyone! What's happening at the kopitiam today?

[orchestrator] β†’ selected: ah_seng
Ah Seng:  Wah, today very hot lah! Must drink more kopi, keep awake lor.
          You all try the new kaya toast or not? Very nice, leh!

[orchestrator] β†’ selected: mei_qi
Mei Qi:   OMG, I haven't tried it yet! 😍 Must go get some later!
          Anyone know if they serve it with half-boiled eggs? That's the best combo, yasss! πŸ₯šβœ¨

[orchestrator] β†’ selected: bala
Bala:     Ah, the new kaya toast β€” an intriguing variable in our breakfast equation.
          If they serve it with half-boiled eggs, satisfaction levels would increase by a
          significant margin, statistically speaking. Must try later, confirm!

[summarizer] β†’ Kopitiam Conversation Summary:
  Key topics: Hot weather, new kaya toast, ideal breakfast combos.
  Dynamics: Casual and lively, mix of enthusiasm and dry analytical humour.
  Mood: Light-hearted and jovial.

[evaluator] β†’ Score: 5/5
  "A lively and authentic kopitiam exchange with strong character voices and natural topic flow."

Alamak, what are you waiting for? Go run it lah! β˜•

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages