The Reasoning Architecture
How a question is put to a model affects the quality of the answer. Asking for step-by-step reasoning, showing worked examples, breaking a task into stages, letting the model use tools or exploring several lines of reasoning all improve results on different kinds of problem. These are known as prompting strategies.
The astra.langchain4j.reasoning package implements a set of prompting strategies as ASTRA agent classes. An agent adopts a strategy by extending its class and adopting the !solve(...) goal. Each strategy is written in ASTRA, so its control flow is a set of plans you can read, trace and build on.
The strategy classes
Every strategy extends LLMAgent, so an agent that uses one has a model module to initialize:
LLMAgent
├── BasicPrompting
├── ZeroShotChainOfThought
├── FewShotChainOfThought
├── Pipeline
│ └── MultiStepChainOfThought
├── ReActCore
│ ├── ReAct
│ └── ReActTools
│ ├── ReActMCP
│ └── ReActCapabilities
├── ThreadOfThoughts
└── TreeOfThoughts
Class |
Strategy |
Model calls |
Configured by |
|---|---|---|---|
|
Ask for the answer directly |
1 |
— |
|
Ask the model to reason step by step before answering |
1, or 2 if the answer must be extracted |
— |
|
Show worked examples, then ask the question |
1 |
|
|
Run a sequence of steps, each a separate call that sees the earlier outputs |
One per step |
|
|
A pipeline of reasoning steps followed by answer extraction |
One per step, plus 1 |
Default steps, or your own |
|
Alternate between reasoning and calling tools |
One per turn, up to |
|
|
Work through a long context one part at a time, keeping notes |
One per part, plus 1 |
The list of parts passed to |
|
Propose and score alternative steps, keeping the best paths |
Many; see Tree of Thoughts |
|
The solve contract
Each strategy handles the same goal:
!solve(string question, string answer)
The question is bound when the goal is adopted, and the answer is bound when the goal completes. ThreadOfThoughts adds a list of context parts: !solve(question, parts, answer). Pipeline offers a lower-level goal, !runPipeline(input, transcript), which returns the outputs of all its steps rather than an answer.
Because the goal is the same, changing strategy is a matter of changing the class an agent extends. This agent answers a question directly:
import astra.langchain4j.reasoning.*;
agent Direct extends BasicPrompting {
module System S;
rule +!main(list args) {
try {
model.loadApiKeyFromEnv("OPENAI_API_KEY");
model.initialize("gpt-4o-mini",
"astra.langchain4j.configuration.OpenAiConfiguration", []);
!solve("Which planet is closest to the Sun?", string answer);
console.println("Answer: " + answer);
} recover {
console.println("The question could not be answered.");
}
S.exit();
}
}
Replacing BasicPrompting with ZeroShotChainOfThought makes the same agent reason step by step; nothing else changes. The strategy classes already declare console (a Console module), so the agent uses it without declaring its own.
Plans that use a strategy follow the same pattern:
Initialize
modelbefore adopting!solve(...).Supply any beliefs the strategy needs, either as
initialbeliefs or by adding them before!solve(...)is adopted.Adopt
!solve(...)insidetry/recover, or handle^!solve(...), so that a provider error does not leave the agent stuck.
Answers and fallbacks
The strategies ask the model to enclose its final answer, and nothing else, in <answer></answer> tags, then extract it with a response template. Models do not always follow the format. When the tags are missing, each strategy falls back rather than failing:
BasicPrompting,FewShotChainOfThought,MultiStepChainOfThought,ThreadOfThoughtsandTreeOfThoughtsuse the whole reply as the answer.ZeroShotChainOfThoughtmakes a second call that asks the model to state the answer, following the original paper’s answer-extraction step.ReActasks the model to try again, and gives up aftermaxStepsturns.
An answer is therefore always text produced by the model. Check it before acting on it.
Run the examples
The examples on the following pages are in a single project. Each agent uses OpenAI through the OPENAI_API_KEY environment variable; change the model and configuration class to use another provider.
Unzip it, set OPENAI_API_KEY and run an example by name:
mvn -Dastra.main=Direct
Example |
Strategy |
Page |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Each run makes one or more remote model calls and can incur provider charges. Most strategies print the model’s intermediate output, so you can follow the reasoning as it happens. The answers vary between runs. To run the examples without a provider, use a scripted model.
Choosing a strategy
Start with the simplest strategy that works, and measure before moving to a more expensive one:
Use direct prompting for lookups, classification and short factual questions.
Use chain of thought when the answer requires a few steps of arithmetic or logic. Add few-shot examples when you need the model to reason in a particular way or produce a particular form of answer.
Use a pipeline when a task has distinct stages that benefit from separate attention, such as understand, plan, solve and check.
Use ReAct when the model needs information or actions it does not have, and the agent can provide them as tools.
Use Thread of Thoughts when the context is too long or too noisy to present at once.
Use Tree of Thoughts when early choices matter and a wrong step is hard to recover from, such as puzzles and planning problems.
The more elaborate strategies make more calls, take longer and cost more. A ReAct or TreeOfThoughts agent can make tens of calls for one question.
Origin of the reasoning agents
The reasoning agents were developed in the Pipelines project, which compares the strategies on the same problems, and moved into astra-langchain4j in ASTRA 2.3.0 so that applications can share them. Pipelines and the A2A example now use the library versions.
Choosing a ReAct implementation
The current ReAct family shares turn control through ReActCore. Select a subclass according to the interface through which the model requests actions.
Class |
Model interface |
Application responsibilities |
|---|---|---|
|
Text containing an action name and input |
Advertise tools with beliefs and implement string-based |
|
Structured tool calls with JSON arguments and call IDs |
Register callable goals or tool declarations; supply remote execution when needed. |
|
Structured tool calls |
Configure MCP connections; local callable goals use the same catalogue. |
|
Structured calls to semantic capability names |
Bind MCP, UTCP, A2A or OpenAPI operations and register native callable goals. |
ReActTools executes callable goals directly. Other local registrations use !invoke handlers; remote registrations need a !remoteTool implementation. A source-neutral ToolSource Java adapter can provide declarations and execution: the ToolSources module lets ASTRA inspect names, descriptions and schemas, then explicitly expose or hide individual tools. The Python benchmark bridge is an application adapter, not a Python dependency of the reasoning library.
Use text ReAct when an existing application deliberately controls a textual protocol. Use structured tools when schemas and provider tool-call messages suit the application. Use MCP for an MCP-specific integration, and capabilities when a conversation mixes implementations. None of these choices guarantees better task performance; evaluate them on the same tasks and model.
All these classes inherit run management. Structured tools can also consume scoped reasoning memory. See unified capabilities and MAMS integrations for the infrastructure choices.