{"slug": "solon-4-0-reactagent-a-practical-guide-to-building-ai-agents-that-think-and-act", "title": "Solon 4.0 ReActAgent: A Practical Guide to Building AI Agents That Think and Act", "summary": "Solon 4.0's ReActAgent enables developers to build AI agents that reason, use external tools, and adapt based on real-world feedback. The agent implements a cognitive loop of thought, action, and observation, and has been used in production for automated customer support and data analysis. A practical guide demonstrates creating tools and agents with the Solon framework.", "body_md": "If you've ever wanted an AI that doesn't just chat but actually *does things* — queries databases, calls APIs, makes decisions, and learns from results — you're in the right place.\n\nIn this tutorial, I'll show you how to build production-ready AI agents using Solon 4.0's `ReActAgent`\n\n. By the end, you'll have built an agent that can reason through complex problems, use external tools, and adapt its behavior based on real-world feedback.\n\nTraditional LLMs are great at generating text, but they hit a wall when they need to interact with the real world — checking a database, fetching live data, or performing calculations.\n\n`ReActAgent`\n\n(Reason + Act) breaks through that wall. It implements a cognitive loop:\n\n```\nThought → Action → Observation → (repeat or finish)\n```\n\nThe agent **thinks** about what to do next, **acts** by calling a tool, **observes** the result, and decides whether to continue or deliver the final answer.\n\nThis isn't just theory. Solon's `ReActAgent`\n\nhas been used in production for automated customer support, intelligent data analysis, and multi-step workflow automation.\n\nFirst, add the `solon-ai-agent`\n\nmodule to your project:\n\n```\n<dependency>\n    <groupId>org.noear</groupId>\n    <artifactId>solon-ai-agent</artifactId>\n</dependency>\n```\n\nNote: If you're using Solon's parent POM, the version is managed automatically. Otherwise, use the latest Solon version.\n\nEvery agent needs a \"brain\" — a `ChatModel`\n\nthat powers reasoning. Let's build one using the fluent API:\n\n``` python\nimport org.noear.solon.ai.chat.ChatModel;\n\nChatModel chatModel = ChatModel.of(\"https://api.moark.com/v1/chat/completions\")\n        .apiKey(\"your-api-key-here\")\n        .model(\"Qwen3-32B\")\n        .build();\n```\n\nYou can also configure it via YAML and inject it:\n\n```\nsolon.ai.chat:\n  demo:\n    apiUrl: \"http://127.0.0.1:11434/api/chat\"\n    provider: \"ollama\"\n    model: \"llama3.2\"\n@Inject(\"${solon.ai.chat.demo}\")\nChatConfig chatConfig;\n\nChatModel chatModel = ChatModel.of(chatConfig).build();\n```\n\nLet's start simple. Create a tool and a basic agent:\n\n``` python\nimport org.noear.solon.ai.agent.react.ReActAgent;\nimport org.noear.solon.ai.annotation.ToolMapping;\nimport org.noear.solon.ai.annotation.Param;\nimport org.noear.solon.ai.chat.tool.AbsToolProvider;\n\nimport java.time.LocalDateTime;\n\n// 1. Define a tool\npublic class TimeTool extends AbsToolProvider {\n    @ToolMapping(description = \"Get the current date and time\")\n    public String getCurrentTime() {\n        return LocalDateTime.now().toString();\n    }\n}\n\n// 2. Build and run the agent\npublic class HelloAgent {\n    public static void main(String[] args) throws Throwable {\n        ChatModel chatModel = ChatModel.of(\"https://api.moark.com/v1/chat/completions\")\n                .apiKey(\"***\")\n                .model(\"Qwen3-32B\")\n                .build();\n\n        ReActAgent agent = ReActAgent.of(chatModel)\n                .role(\"You are a helpful assistant that can check the time and date.\")\n                .defaultToolAdd(new TimeTool())\n                .build();\n\n        String response = agent.prompt(\"What time is it right now?\")\n                .call()\n                .getContent();\n\n        System.out.println(response);\n    }\n}\n```\n\nWhen you run this, the agent will:\n\n`getCurrentTime`\n\ntool.\"`getCurrentTime()`\n\n.Let's build something more practical — a support agent that can query an order database and check inventory.\n\n``` python\nimport org.noear.solon.ai.chat.tool.AbsToolProvider;\nimport org.noear.solon.ai.annotation.ToolMapping;\nimport org.noear.solon.ai.annotation.Param;\n\npublic class OrderTool extends AbsToolProvider {\n\n    @ToolMapping(description = \"Query order status by order ID\")\n    public String getOrderStatus(@Param(description = \"The order ID\") String orderId) {\n        // Simulate database lookup\n        if (\"ORD-1001\".equals(orderId)) {\n            return \"Order ORD-1001: SHIPPED, estimated delivery July 7\";\n        } else if (\"ORD-1002\".equals(orderId)) {\n            return \"Order ORD-1002: PENDING, payment not confirmed\";\n        }\n        return \"Order not found: \" + orderId;\n    }\n\n    @ToolMapping(description = \"Check product inventory by product ID\")\n    public String checkInventory(@Param(description = \"The product SKU\") String sku) {\n        // Simulate inventory check\n        if (\"SKU-A100\".equals(sku)) {\n            return \"In stock: 42 units\";\n        } else if (\"SKU-B200\".equals(sku)) {\n            return \"Low stock: 3 units remaining\";\n        }\n        return \"Product not found: \" + sku;\n    }\n}\nReActAgent supportAgent = ReActAgent.of(chatModel)\n        .name(\"customer_support\")\n        .role(\"Customer Support Agent — you handle order inquiries and inventory checks.\")\n        .defaultToolAdd(new OrderTool())\n        .maxTurns(8)                    // Max reasoning steps\n        .autoRethink(true)              // Auto-rethink when stuck\n        .retryConfig(3, 1000L)          // Retry 3 times, 1s delay\n        .modelOptions(options -> {\n            options.temperature(0.1);   // Low temperature for deterministic decisions\n        })\n        .build();\n\nString result = supportAgent.prompt(\"Customer ORD-1002 wants to know when their order will arrive. Can you check?\")\n        .call()\n        .getContent();\n\nSystem.out.println(result);\n```\n\nThe agent will:\n\n`ORD-1002`\n\n`getOrderStatus(\"ORD-1002\")`\n\nIn production, you need visibility into what your agent is thinking. `ReActInterceptor`\n\ngives you lifecycle hooks:\n\n``` python\nimport org.noear.solon.ai.agent.react.ReActInterceptor;\nimport org.noear.solon.ai.agent.react.ReActTrace;\nimport org.noear.solon.ai.agent.react.task.ToolExchanger;\n\nReActAgent observableAgent = ReActAgent.of(chatModel)\n        .name(\"observable_agent\")\n        .role(\"I help with various tasks.\")\n        .defaultToolAdd(new OrderTool())\n        .defaultInterceptorAdd(new ReActInterceptor() {\n\n            @Override\n            public void onAgentStart(ReActTrace trace) {\n                System.out.println(\"🤖 Agent started. Prompt: \" + trace.getOriginalPrompt().getUserContent());\n            }\n\n            @Override\n            public void onThought(ReActTrace trace, String thoughtContent,\n                                   AssistantMessage assistantMessage) {\n                System.out.println(\"💭 Thinking: \" + thoughtContent);\n            }\n\n            @Override\n            public void onAction(ReActTrace trace, ToolExchanger toolExchanger) {\n                System.out.println(\"🛠️  Tool: \" + toolExchanger.getToolName()\n                        + \", args: \" + toolExchanger.getArgs());\n            }\n\n            @Override\n            public void onObservation(ReActTrace trace, ToolExchanger toolExchanger,\n                                       ChatMessage observation, Throwable error,\n                                       long durationMs) {\n                if (error != null) {\n                    System.err.println(\"❌ Tool failed: \" + error.getMessage());\n                } else {\n                    System.out.println(\"✅ Tool result in \" + durationMs + \"ms\");\n                }\n            }\n\n            @Override\n            public void onAgentEnd(ReActTrace trace) {\n                System.out.println(\"✅ Agent finished.\");\n            }\n        })\n        .build();\n```\n\nThis gives you a full audit trail of every decision your agent makes.\n\nFor long-running tasks, use `stream()`\n\nto get real-time output:\n\n```\nagent.prompt(\"Analyze our top 10 products and give me a sales summary.\")\n     .stream()\n     .doOnNext(resp -> {\n         System.out.print(resp.getMessage().getContent());\n     })\n     .doOnComplete(() -> {\n         System.out.println(\"\\n✅ Analysis complete!\");\n     })\n     .subscribe();\n```\n\nYou can fine-tune behavior for individual calls using `.options()`\n\n:\n\n```\nagent.prompt(\"Analyze this complex dataset and generate a JSON report.\")\n     .session(mySession)                // Reuse an existing session\n     .options(o -> o\n         .maxTurns(15)                  // More turns for complex tasks\n         .planningMode(true)            // Enable planning phase\n         .temperature(0.3)              // Balance creativity and precision\n         .outputSchema(\"{\\\"type\\\":\\\"object\\\",\\\"properties\\\":{...}}\")  // Structured output\n         .toolAdd(new ReportingTool())  // Add temporary tool for this call\n     )\n     .call();\n```\n\n| Category | Method | Description | Default |\n|---|---|---|---|\n| Control | `maxTurns(int)` |\nMax reasoning steps | 8 |\n| Control | `autoRethink(boolean)` |\nEnable auto-rethink | false |\n| Control | `retryConfig(int, long)` |\nRetry count & delay | 3, 1000ms |\n| Model | `temperature(double)` |\nRandomness (0-2) | 0.5 |\n| Model | `max_tokens(long)` |\nMax tokens to generate | — |\n| Tools | `toolAdd(FunctionTool)` |\nAdd tool temporarily | — |\n| Tools | `talentAdd(Talent)` |\nAdd talent/skill | — |\n| Extension | `interceptorAdd(interceptor)` |\nAdd interceptor | — |\n\n`ReActAgent`\n\nsessions enable long-running conversations with memory:\n\n``` python\nimport org.noear.solon.ai.agent.session.InMemoryAgentSession;\nimport org.noear.solon.ai.agent.AgentSession;\n\n// Create or reuse a session\nAgentSession session = InMemoryAgentSession.of(\"user-session-123\");\n\n// First turn\nString r1 = agent.prompt(\"Find me products under $50\")\n        .session(session)\n        .call()\n        .getContent();\n\n// Second turn (agent remembers context)\nString r2 = agent.prompt(\"What's the shipping time for the cheapest one?\")\n        .session(session)\n        .call()\n        .getContent();\n\n// Inspect the trace\nReActTrace trace = agent.getTrace(session);\nSystem.out.println(\"Total steps: \" + trace.getStepCount());\n\n// Or get a formatted summary\nSystem.out.println(trace.getFormattedHistory());\n```\n\nThe trace object gives you:\n\n`getFormattedHistory()`\n\n)`getStepCount()`\n\n, `getMetrics()`\n\n)`getOriginalPrompt()`\n\n, `getSession()`\n\n)Not all models support native tool calls. `ReActAgent`\n\nsupports **Text ReAct** mode — it uses regex to extract `Action: {json}`\n\ntags from the model's text output. This makes it compatible with smaller, lighter models that don't have native tool-call support — perfect for edge deployments and cost-sensitive scenarios.\n\nThe execution style (`ReActStyle`\n\n) is configured at build time through `ReActAgentConfig`\n\n, choosing between `ReActStyle.NATIVE`\n\n(OpenAI-style tool_calls, default) and the lightweight `ReActStyle.TEXT`\n\napproach. When using Text mode, the agent parses `Action: {json}`\n\ntags from the model's output and executes the corresponding tool.\n\nHere's a full, copy-paste-ready example:\n\n``` python\nimport org.noear.solon.ai.agent.react.ReActAgent;\nimport org.noear.solon.ai.agent.react.ReActInterceptor;\nimport org.noear.solon.ai.agent.react.ReActTrace;\nimport org.noear.solon.ai.agent.react.task.ToolExchanger;\nimport org.noear.solon.ai.annotation.ToolMapping;\nimport org.noear.solon.ai.annotation.Param;\nimport org.noear.solon.ai.chat.ChatModel;\nimport org.noear.solon.ai.chat.tool.AbsToolProvider;\nimport org.noear.solon.ai.chat.message.ChatMessage;\n\npublic class ECommerceSupportApp {\n    public static void main(String[] args) throws Throwable {\n        // 1. Build the model\n        ChatModel model = ChatModel.of(\"https://api.moark.com/v1/chat/completions\")\n                .apiKey(\"${API_KEY}\")\n                .model(\"Qwen3-32B\")\n                .build();\n\n        // 2. Build the agent\n        ReActAgent agent = ReActAgent.of(model)\n                .name(\"ecommerce_support\")\n                .role(\"E-commerce Support Agent\")\n                .defaultToolAdd(new OrderTool())\n                .defaultToolAdd(new InventoryTool())\n                .defaultInterceptorAdd(new LoggingInterceptor())\n                .maxTurns(10)\n                .autoRethink(true)\n                .build();\n\n        // 3. Run\n        String answer = agent.prompt(\n                \"Customer wants to order SKU-A100 but saw ORD-1001 hasn't arrived yet. \" +\n                \"Check both and explain the situation.\"\n        ).call().getContent();\n\n        System.out.println(answer);\n    }\n}\n\n// Tools\nclass OrderTool extends AbsToolProvider {\n    @ToolMapping(description = \"Query order status by order ID\")\n    public String getOrderStatus(@Param(description = \"Order ID\") String orderId) {\n        // Your database logic here\n        return \"ORD-1001: SHIPPED\";\n    }\n}\n\nclass InventoryTool extends AbsToolProvider {\n    @ToolMapping(description = \"Check product inventory by SKU\")\n    public String checkStock(@Param(description = \"Product SKU\") String sku) {\n        // Your inventory logic here\n        return \"SKU-A100: 42 units in stock\";\n    }\n}\n\n// Interceptor\nclass LoggingInterceptor implements ReActInterceptor {\n    @Override\n    public void onThought(ReActTrace trace, String thought,\n                          AssistantMessage msg) {\n        System.out.println(\"💭 \" + thought);\n    }\n    @Override\n    public void onAction(ReActTrace trace, ToolExchanger tool) {\n        System.out.println(\"🛠️  \" + tool.getToolName());\n    }\n}\n```\n\n`@ToolMapping`\n\nand `@Param`\n\nannotations — simple POJOs.Solon's `ReActAgent`\n\nbrings production-grade AI agent capabilities to the Java ecosystem with minimal boilerplate. The same framework design philosophy — **restraint, efficiency, openness** — applies here: you get powerful agent capabilities without framework lock-in.\n\n*Want to learn more? Check out the official Solon AI Agent documentation or explore the solon-ai GitHub repo.*", "url": "https://wpnews.pro/news/solon-4-0-reactagent-a-practical-guide-to-building-ai-agents-that-think-and-act", "canonical_source": "https://dev.to/solonjava/solon-40-reactagent-a-practical-guide-to-building-ai-agents-that-think-and-act-4ji8", "published_at": "2026-07-04 00:51:04+00:00", "updated_at": "2026-07-04 01:18:48.455968+00:00", "lang": "en", "topics": ["ai-agents", "large-language-models", "developer-tools"], "entities": ["Solon", "ReActAgent", "Qwen3-32B", "Ollama", "Moark"], "alternates": {"html": "https://wpnews.pro/news/solon-4-0-reactagent-a-practical-guide-to-building-ai-agents-that-think-and-act", "markdown": "https://wpnews.pro/news/solon-4-0-reactagent-a-practical-guide-to-building-ai-agents-that-think-and-act.md", "text": "https://wpnews.pro/news/solon-4-0-reactagent-a-practical-guide-to-building-ai-agents-that-think-and-act.txt", "jsonld": "https://wpnews.pro/news/solon-4-0-reactagent-a-practical-guide-to-building-ai-agents-that-think-and-act.jsonld"}}