AI Agent Decision Support System
Commissioned Work
Overview
Designed and built a B2B SaaS AI agent that argues the same decision independently from the Pro and Con sides, raising the quality of executive decisions. An 11-node stateful workflow on LangGraph conditionally drives internal knowledge search (Vertex AI Search) and web search (Tavily), with memory isolated per company / user / thread for a multi-tenant deployment.
Architecture
LLM-heavy reasoning (Pro / Con inference) is cleanly separated from the deterministic pre- and post-processing (memory load, search-necessity judgment, context merge, memory save). The graph uses conditional branching, and `judge_search_need` decides up front whether search is needed at all — avoiding wasted latency and tokens on unnecessary calls.
Pro / Con dual agents are implemented as a ReAct (Reasoning + Acting) pattern in `backend/src/agent/react_agent.py`. Both agents read the same context independently and build citation-backed arguments, invoking Vertex AI Search or Gemini built-in tools as they go.
Graph state is strictly typed with `TypedDict(total=False)` — OverallState / UserMemory / CompanyInfo / Citation — so type mismatches between nodes surface under Python type checking and regression cost stays flat as the graph grows.
- load_user_memory: load history and profile per user, thread, and company
- judge_search_need: decide up front whether external search is needed
- rag_search / web_search / merge_context: run Vertex AI Search and Tavily in parallel only when needed and merge contexts
- set_prompt → route_agents: build Pro and Con prompts appropriate to the context
- pro_agent / con_agent (parallel): independently produce arguments via ReAct
- merge_responses → assign_variables → finalize_answer: merge responses, bind variables, and finalize
- save_user_memory: persist conversation, decision history, and updated preferences
Multi-Tenancy & Memory
Queries against Vertex AI Search attach `company_id` as a metadata filter, isolating knowledge and access per company. The system handles the N-to-N relationship where a single user belongs to multiple companies, so switching companies safely swaps both the search scope and the referenced documents.
Conversation history uses LangGraph MemorySaver and InMemoryStore scoped per user / thread, and `trimming.py` caps history at 800,000 tokens by evicting oldest messages first — long-running threads never blow the context budget.
`BANSO_` -prefixed environment variables override `backend/src/agent/config.py` at runtime, so per-tenant configuration can be injected without code changes. Citations normalize source URLs and attach them to responses so evidence is always reachable.
Development & Quality
A Dev Container (Ubuntu Noble + Zsh + uv + Node.js v24) provides a uniform development environment. pytest / pytest-asyncio / pytest-cov cover unit, async, and coverage testing, and LangSmith provides LLM tracing and monitoring, so CI can assert that graph changes propagate as intended.