Two routes, same wallet-risk model.
A FastAPI backend with two paths to the same analysis: a hard-coded Python pipeline, and a single-turn LangChain tool-calling agent over the same primitives. Same Pydantic JSON contract, two different ways the system reaches it.
The problem
An MVP for wallet-risk analysis that’s honest about what’s deterministic and what’s a model call. Recruiters and engineers should be able to clone, run locally, hit Swagger, and immediately see the difference between the two routes — without an OpenAI key being mandatory to get useful output.
What I built
POST /analyze-wallet— fixed pipeline. Resolves data (mock or user-suppliedmock_transactions), runsextract_features, thenanalyze_wallet_ai(rules-only without a key, OpenAI JSON with rule fallback when keyed). ReturnsAnalyzeWalletResponsein every case.POST /agent/analyze-wallet— single-turn LangChainAgentExecutorwith threeStructuredTools (get_wallet_transactions,extract_wallet_features,assess_wallet_risk). The model picks which to call from a natural-language question.- Post-processing layer that splits agent output into
agent_answer(human-readable),risk_assessment(structured, same shape asai_analysis), andtool_trace(one-line per tool, no raw JSON dumps). - pytest coverage for both routes — including stubbed agents for the schema tests so most of the suite runs without the LLM.
What I’d defend
The architectural decision: keep the fixed pipeline explicitly coded so its behavior is predictable and CI-testable, and let the agent path be the place where the LLM owns control flow. Same services power both, so swapping the mock data layer for a real chain adapter only happens once.