Standard monolithic Large Language Model (LLM) prompts fall short when faced with complex, multi-stage enterprise procedures. For workflows such as financial credit approvals or audit pipelines, tasks must be routed based on strict compliance rules, pass through hierarchical review loops, and sometimes return to previous steps for corrections.
In this article, we demonstrate how to build and orchestrate a multi-stage Financial Loan Approval Pipeline using Solon AI (v4.0.5). We will leverage its native integration with the lightweight state machine engine Solon Flow, utilizing nested agents, conditional routing (Exclusive Junctions), and state verification.
The Architecture: Nested Agents and Flow Orchestration
Our credit approval system simulates a real-world enterprise workflow:
- Entry Officer (KYC): Performs preliminary verification and data formatting.
-
Loan Architect: Automatically designs loan products, interest rates, and loan terms, rating risk levels (
high,mid,low). - Risk Control Center (Nested Team): A specialized, nested sub-agent committee that acts as a unified black box. Inside, a Credit Department sub-team, a Quota Calculator, and a Legal Compliance Officer collaborate.
- Stress Tester: Run simulations to assess risk and default probabilities. If tests fail, the workflow loops back to the Risk Control Center for term rectification.
- Security Auditor: Conducts final checks to ensure GDPR compliance.
-
Final Approver: Signs off on the final decision, determining the disbursement route (
canaryorfull).
Here is a simplified flowchart of the process:
[Start] -> (Entry Officer) -> (Loan Architect) -> [Junction: Risk Level?]
|
+---------------------------+ (high risk)
| |
v v (low/mid risk)
((Risk Control Center)) ----> (Stress Tester)
^ |
| v
+----------------- [Junction: Passed?] (no)
| (yes)
v
(Security Auditor)
|
v
(Final Approver) -> [Disbursement Route] -> [End]
Implementing the Workflow in Solon AI
Let us implement this pipeline in Java using Solon AI.
Step 1: Initialize Chat Model and Define ReAct Agents
First, we set up our LLM backend and instantiate the individual professional agents using ReActAgent:
import org.noear.solon.ai.chat.ChatModel;
import org.noear.solon.ai.agent.react.ReActAgent;
// Initialize the ChatModel
ChatModel chatModel = ChatModel.of("https://api.your-provider.com/v1/chat/completions")
.apiKey("your-api-key")
.model("your-model-name")
.build();
// Entry Officer (KYC Analyst)
ReActAgent entryOfficer = ReActAgent.of(chatModel)
.name("entry_officer")
.role("Loan Intake Officer")
.instruction("Verify customer identity. Structure the application details cleanly. Ask for missing details.")
.build();
// Loan Architect
ReActAgent loanArchitect = ReActAgent.of(chatModel)
.name("loan_architect")
.role("Credit Product Architect")
.instruction("Calculate DTI (Debt-to-Income). Assign preliminary risk label: [high, mid, low].")
.build();
Step 2: Build a Nested Risk Control Committee
In Solon AI, a TeamAgent acts as a multi-agent container. Since a team is itself an instance of Agent, it can be nested inside another TeamAgent seamlessly:
import org.noear.solon.ai.agent.team.TeamAgent;
// Risk Control Center containing multiple internal experts and a nested Credit Dept sub-team
TeamAgent riskControlCenter = TeamAgent.of(chatModel)
.name("risk_control_center")
.role("Deep Risk and Compliance Assessment Group")
.agentAdd(
// Nested sub-team (Credit Department)
TeamAgent.of(chatModel)
.name("credit_dept")
.agentAdd(
ReActAgent.of(chatModel)
.name("big_data_analyst")
.role("Big Data Risk Modeler")
.instruction("Analyze social behavior patterns and alternative credit scores.")
.build()
).build(),
// Quota Calculator
ReActAgent.of(chatModel)
.name("quota_calculator")
.role("Credit Actuary")
.instruction("Evaluate collateral and financial records to determine the final credit line.")
.build(),
// Legal Compliance Officer
ReActAgent.of(chatModel)
.name("legal_reviewer")
.role("AML Auditor")
.instruction("Cross-check PEP lists and perform Anti-Money Laundering verification.")
.build()
).build();
Step 3: Define Post-Assessment and Audit Agents
Next, define the validation, stress-testing, and sign-off actors:
ReActAgent riskStressTester = ReActAgent.of(chatModel)
.name("risk_stress_tester")
.role("Stress Test Engineer")
.instruction("Simulate default probability under a +200BP interest rate shock. Set 'passed' to true/false.")
.build();
ReActAgent securityAuditor = ReActAgent.of(chatModel)
.name("security_auditor")
.role("Data Privacy Auditor")
.instruction("Verify that user data handling adheres to GDPR and national data security laws.")
.build();
ReActAgent finalApprover = ReActAgent.of(chatModel)
.name("final_approver")
.role("General Manager of Credit Division")
.instruction("Synthesize the stress test and security audit to output a final strategy: [canary, full, reject].")
.build();
Step 4: Programmatic Flow Orchestration (graphAdjuster)
Now, orchestrate the interaction flow. By default, TeamAgent uses collaborative protocols (e.g., Sequential, Swarm), but for strict corporate governance, we override the topology using .graphAdjuster to build a directed workflow graph:
TeamAgent creditApprovalSystem = TeamAgent.of(chatModel)
.name("credit_approval_system")
.graphAdjuster(spec -> {
// Start node routes directly to the KYC officer
spec.addStart("start").linkAdd("entry_officer");
spec.addActivity(entryOfficer).linkAdd("loan_architect");
// Define conditional branching: high-risk applications route to the Wind Control Center
spec.addExclusive("exc_risk_level")
.linkAdd("risk_control_center", l -> l.when("risk == 'high'"))
.linkAdd("risk_stress_tester");
// Wind Control Center transitions to the Stress Tester
spec.addActivity(riskControlCenter).linkAdd("risk_stress_tester");
// Stress Tester branches depending on outcome. If failed (passed == false), return to Risk Center
spec.addActivity(riskStressTester).linkAdd("exc_test_result");
spec.addExclusive("exc_test_result")
.linkAdd("risk_control_center", l -> l.when("passed == false"))
.linkAdd("security_auditor");
spec.addActivity(securityAuditor).linkAdd("final_approver");
spec.addActivity(finalApprover).linkAdd("exc_release");
// Branch to final disbursement channels
spec.addExclusive("exc_release")
.linkAdd("canary_disburser", l -> l.when("route == 'canary'"))
.linkAdd("full_disburser", l -> l.when("route == 'full'"))
.linkAdd("end");
spec.addActivity("canary_disburser").title("Canary Disbursement Channel").linkAdd("end");
spec.addActivity("full_disburser").title("Full-Scale Disbursement Channel").linkAdd("end");
spec.addEnd("end");
}).build();
Running the Workflow with State Tracking
To execute our orchestrated agent network, we maintain a persistent state using AgentSession. This keeps a record of variables like risk, passed, and route, ensuring they flow correctly between nodes:
import org.noear.solon.ai.agent.AgentSession;
import org.noear.solon.ai.agent.session.InMemoryAgentSession;
import org.noear.solon.ai.chat.prompt.Prompt;
import org.noear.solon.ai.agent.team.TeamTrace;
public class App {
public static void main(String[] args) throws Throwable {
// Create an in-memory session for a loan application
AgentSession session = InMemoryAgentSession.of("LOAN_ID_2026_999");
String query = "【Urgent Loan Application】\n" +
"Applicant: Wang Wu (Business Owner)\n" +
"Purpose: 8 Million RMB for raw materials purchasing.\n" +
"Note: Since this involves cross-border trade and a high limit, " +
"please tag this as high risk. Verify disbursement via the Canary channel.";
// Run the workflow
String report = creditApprovalSystem.prompt(Prompt.of(query))
.session(session)
.call()
.getContent();
System.out.println("=== Final Execution Report ===\n" + report);
// Inspect and trace the execution path
TeamTrace trace = creditApprovalSystem.getTrace(session);
System.out.println("\n=== Execution Path Trace ===");
trace.getRecords().forEach(step ->
System.out.println("Executed Node: [" + step.getSource() + "]")
);
}
}
Sample Traced Path Output:
=== Execution Path Trace ===
Executed Node: [entry_officer]
Executed Node: [loan_architect]
Executed Node: [risk_control_center]
Executed Node: [risk_stress_tester]
Executed Node: [security_auditor]
Executed Node: [final_approver]
Executed Node: [canary_disburser]
Due to the applicant's status and the high transaction limit, the system dynamically rerouted the execution thread through the risk_control_center before performing stress tests and releasing funds through the canary gateway.
Architectural Advantages
-
Deterministic Controls for Nondeterministic LLMs: By defining structured states (using
addExclusiveand SpEL conditions), the workflow enforces rigid banking boundaries while allowing LLMs to handle unstructured text processing within individual nodes. -
Specialized Division of Labor: High-risk calculations are isolated inside the
risk_control_centermicro-agent block. General tasks skip this step, optimizing latency and saving LLM token costs. -
Dynamic Loop Corrections: If the stress test flags default vulnerability (
passed == false), the agent automatically returns to the risk center to renegotiate security collaterals without crashing the session. -
Complete Trace Audits: Since every single execution step is tracked inside
TeamTrace, banking compliance officers can easily audit the trace record log, mapping out exactly which agent made which decision.
Top comments (0)