Package dyntabs.ai

Class EasyAI

java.lang.Object
dyntabs.ai.EasyAI

public final class EasyAI extends Object
Main entry point for the EasyAI library.

EasyAI is a simple abstraction layer over LangChain4J. It hides all the low-level details (ChatModel, ChatMemory, AiServices, ToolSpecification, EmbeddingStore...) behind a clean builder-pattern API that any Java developer can use in minutes.

Quick Start

Step 1: Add your API key to easyai.properties on the classpath:

 easyai.provider=openai
 easyai.api-key=sk-YOUR-KEY
 easyai.model-name=gpt-4o-mini
 

Step 2: Start chatting!

Three Ways to Use EasyAI

1. Simple Chat (chat())

Send messages to AI and get responses. Optionally remembers conversation history.


 Conversation chat = EasyAI.chat()
     .withMemory(20)                              // remember last 20 messages
     .withSystemMessage("You are a helpful tutor") // set AI personality
     .build();

 String answer = chat.send("What is Java?");
 String follow = chat.send("Give me an example"); // AI remembers the context
 

2. AI Assistant with Tools (assistant(Class))

Define an interface, give it your existing service classes as "tools", and the AI will call your Java methods when needed.


 // 1. Define assistant interface
 @EasyAIAssistant(systemMessage = "You are an e-commerce support bot")
 public interface SupportBot {
     String ask(String question);
 }

 // 2. Your existing service (no AI annotations needed!)
 public class OrderService {
     public String findOrder(String orderId) {
         return database.findById(orderId).toString();
     }
 }

 // 3. Wire it together
 SupportBot bot = EasyAI.assistant(SupportBot.class)
     .withTools(orderService, userService)
     .build();

 // 4. The AI will automatically call orderService.findOrder("12345")
 String answer = bot.ask("Where is my order #12345?");
 

3. Document-Powered Assistant (RAG)

Let the AI answer questions based on your PDF, DOCX, or TXT files.


 @EasyRAG(source = "classpath:company-policy.pdf")
 @EasyAIAssistant(systemMessage = "Answer based on the company policy")
 public interface PolicyBot {
     String ask(String question);
 }

 PolicyBot bot = EasyAI.assistant(PolicyBot.class).build();
 String answer = bot.ask("What is the vacation policy?");
 // AI reads the PDF and answers based on its content
 

Overriding Configuration Per-Call

You can override any config property when building:


 Conversation chat = EasyAI.chat()
     .withProvider("ollama")                       // use local Ollama
     .withModel("llama3")                          // specific model
     .withBaseUrl("http://localhost:11434/v1/")     // custom endpoint
     .withTemperature(0.3)                          // less creative
     .withMaxTokens(500)                            // shorter answers
     .build();
 

CDI / Jakarta EE Integration

In a Jakarta EE application, assistants are automatically injectable:


 @Inject SupportBot bot;  // no manual build() needed
 
See Also:
  • Method Details

    • chat

      public static ConversationBuilder chat()
      Starts building a new Conversation for simple chat.
      
       Conversation chat = EasyAI.chat()
           .withMemory(20)
           .withSystemMessage("You are a helpful assistant")
           .build();
      
       String answer = chat.send("Hello!");
       
      Returns:
      a new ConversationBuilder
    • assistant

      public static <T> AssistantBuilder<T> assistant(Class<T> assistantInterface)
      Starts building an AI Assistant proxy for the given interface.

      The interface should have one or more methods that accept a String and return a String. Annotate it with @EasyAIAssistant for a system message.

      
       @EasyAIAssistant(systemMessage = "You are a code reviewer")
       public interface CodeReviewer {
           String review(String code);
       }
      
       CodeReviewer reviewer = EasyAI.assistant(CodeReviewer.class).build();
       String feedback = reviewer.review("public void foo() { ... }");
       
      Type Parameters:
      T - the assistant interface type
      Parameters:
      assistantInterface - the interface class to create a proxy for
      Returns:
      a new AssistantBuilder
    • agent

      public static AgentBuilder agent()
      Starts building an EasyAgent that autonomously plans and executes multi-step tasks by orchestrating calls to your registered Java services.

      Unlike assistant(Class), which answers a single question, an agent receives a complex task and breaks it into steps — calling your service methods in the right order, using the result of each step to decide what to do next, and adapting if a step fails.

      
       EasyAgent agent = EasyAI.agent()
           .withServices(inventoryService, paymentService, orderService)
           .withMaxSteps(10)
           .withPlanningPrompt(true)
           .withStepListener(step ->
               log.info("[AGENT] Step {}: {}({}) -> {}",
                   step.stepNumber(), step.toolName(),
                   step.arguments(), step.result()))
           .build();
      
       String result = agent.execute(
           "Order 2 laptops for user U123, apply loyalty credit, " +
           "use fallback warehouse WH-EU if out of stock."
       );
       
      Returns:
      a new AgentBuilder
    • extract

      public static <T> ExtractionBuilder<T> extract(Class<T> type)
      Starts a typed extraction: turn unstructured text or a document into a populated Java object (record or POJO).

      This is the bridge from the unstructured/AI world into your normal typed-Java world. Once .from(...) returns, no AI is involved any more — you hold a plain object that your existing services, JPA entities, and PrimeFaces forms already understand.

      
       record Invoice(String vendor, String invoiceNumber, LocalDate date,
                      BigDecimal total, List<LineItem> items) {}
      
       // From free text:
       Invoice inv = EasyAI.extract(Invoice.class).from(emailBody);
      
       // From a document's bytes - parses (Tika) and extracts in one call:
       Invoice inv = EasyAI.extract(Invoice.class)
           .from(DocumentSource.of("invoice.pdf", pdfBytes));
      
       em.persist(inv);   // it is just data from here on
       

      Malformed model output is retried automatically; call .validate() to also run Jakarta Bean Validation on the result.

      Type Parameters:
      T - the type to extract
      Parameters:
      type - the class to extract (a record or POJO)
      Returns:
      a new ExtractionBuilder
      See Also:
    • indexer

      public static IndexerBuilder indexer()
      Starts building a document indexer that persists embeddings into a vector store (currently Milvus), so assistants can retrieve them later.

      This is the write side of RAG. Where assistant(Class) with withRAG(...) builds an ephemeral, in-memory index that vanishes when the JVM stops, indexer() writes to a persistent store you populate once and reuse — think "save the documents to the database" rather than "load them for this request."

      
       // One-time (or scheduled) ingestion:
       int indexed = EasyAI.indexer()
           .toMilvus("localhost", 19530, "documents")
           .index("file:/data/policy.pdf", "classpath:faq.txt");
      
       // Later, any assistant reads from the same collection:
       PolicyBot bot = EasyAI.assistant(PolicyBot.class)
           .withMilvus("localhost", 19530, "documents")
           .build();
       
      Returns:
      a new IndexerBuilder
      See Also:
    • configure

      public static void configure(EasyAIConfig config)
      Sets a global configuration that will be used as default for all new conversations and assistants (unless overridden per-builder).
      
       EasyAI.configure(EasyAIConfig.builder()
           .provider("openai")
           .apiKey("sk-...")
           .modelName("gpt-4o")
           .build());
       
      Parameters:
      config - the global configuration
    • getGlobalConfig

      public static EasyAIConfig getGlobalConfig()
      Returns the global configuration, or loads from easyai.properties if not set.
      Returns:
      the current global EasyAIConfig
    • extractErrorMessage

      public static String extractErrorMessage(Throwable t)
      Extracts a clean, human-readable error message from an AI exception.

      LangChain4J exceptions (especially from OpenAI-compatible providers like Groq, Azure OpenAI, and OpenAI itself) often carry raw JSON in their message, for example:

       {"error":{"message":"Failed to call a function...","type":"invalid_request_error",...}}
       

      This method parses the JSON and extracts only the error.message field. If the message is not JSON, it is returned as-is. If the exception is null, an empty string is returned.

      Typical usage in a JSF backing bean or REST endpoint:

      
       try {
           return bot.ask(userQuestion);
       } catch (Exception e) {
           log.error("AI call failed", e);
           return "Sorry, something went wrong: " + EasyAI.extractErrorMessage(e);
       }
       

      Common LangChain4J exception types to catch separately if needed:

      • dev.langchain4j.exception.AuthenticationException — wrong API key
      • dev.langchain4j.exception.RateLimitException — rate limit exceeded
      • dev.langchain4j.exception.InvalidRequestException — bad request, tool call failure
      • dev.langchain4j.exception.InternalServerException — provider server error
      Parameters:
      t - the exception thrown by an AI assistant or conversation call
      Returns:
      a clean, readable error message