One Agent, Two Frameworks, Zero Lessons About Frameworks A developer who built the same LLM-powered support agent with Spring AI and LangGraph found the frameworks were the least interesting part of the design, as both produced identical Classification records and downstream behavior. The experiment, detailed in Part 6 of a series, showed that the framework only handles turning a customer message into a typed classification, with all subsequent logic—eligibility rules, risk-tier gate, approval queue—remaining unchanged across implementations. The author concludes that if a framework choice changes what a system does, the logic was in the wrong place. One Agent, Two Frameworks, Zero Lessons About Frameworks I implemented the same agent with Spring AI and LangGraph — the boundary didn't move Part 6 findings of an experiment: building an LLM-powered support agent with deterministic boundaries. The companion repo contains the full code. "Spring AI or LangChain?" It's the first question everyone asks, and I implemented the same agent with Spring AI and LangGraph — the boundary didn't move Part 6 findings of an experiment: building an LLM-powered support agent with deterministic boundaries. The companion repo contains the full code. "Spring AI or LangChain?" It's the first question everyone asks, and I answered it the expensive way: I built the same agent twice. The frameworks turned out to be the least interesting decision in the design. Here's what the comparison actually taught, which is not what I expected to write. In both implementations, the framework does exactly one thing: turn a customer message into a Classification — a typed record naming the action and carrying the order id. Everything after that point is identical: eligibility rules, risk-tier gate, approval queue. Same code, same tests, same behaviour. // dev/tonal/support/ai/SpringAiIntentClassifier.java public final class SpringAiIntentClassifier implements IntentClassifier { private final ChatClient chatClient; public SpringAiIntentClassifier ChatClient.Builder chatClientBuilder { this.chatClient = chatClientBuilder .defaultSystem SYSTEM PROMPT .build ; } @Override public Classification classify String customerMessage { return chatClient.prompt .user customerMessage .call .entity Classification.class ; } } That's the entire integration surface — one class, one method. The LangGraph version differs only in mechanics: Concern Spring AI LangGraph Tool definition @Tool on methods @tool decorator Structured output .entity Classification.class response format=Pydantic Orchestration loop Advisor chain ReAct graph Provider swap Starter dependency Model class flowchart LR subgraph S "Spring AI implementation" direction LR S1 "ChatClient prompt" -- S2 ".entity Classification " end subgraph L "LangGraph implementation" direction LR L1 "ReAct agent" -- L2 "structured response" end S2 -- P "Classification record" L2 -- P P -- G{"GatedActionService