Work
Real clients, real constraints,
every architecture decision documented.
AICA — AI Academic & Career Guidance Platform
- React
- Vite
- TypeScript
- Bun
- Express
- MongoDB
- OpenAI
- Hugging Face
- Zod
- Better Auth
Problem
Career guidance products fail in a specific way: they give fluent answers that cannot be verified. A recommendation that cannot be explained at the scoring level is not a guidance system — it is a confident guess in a chat interface. AICA needed pathway recommendations that were defensible before any LLM was involved in the explanation.
Insight
A single streaming pass cannot execute a tool call and stream the answer simultaneously. The model begins emitting tokens the moment the stream opens. Interrupting mid-generation to run a search, absorb the results, and resume coherently breaks the context the generation was already conditioned on. Most implementations deal with this by either buffering the entire response (losing the streaming UX) or skipping real search (hallucinating sources). AICA separates the passes.
Recommendation scoring is rule-based, not LLM-generated.
The tradeoff is explicit: less flexibility in edge cases, full determinism and auditability in every case. Each scored recommendation carries a structured reasons[] array built during scoring — 'Strong match: prefers quantitative work', 'Partial: on-site heavy but you prefer remote'. The LLM enters only when the user asks for depth, and at that point it has evidence to work from, not a float to rationalize.
Contracts live in a shared Zod layer, not duplicated across client and server.
TypeScript types and runtime validators are colocated in a /contracts package imported by both client and server. The contract is the single source of truth — schema drift between ends becomes a compile error rather than a runtime failure discovered after deployment.
Outcome
A guidance product where every recommendation has a structured reason behind it, the Advisor spends inference budget only where it earns its cost, and the streaming answer is grounded in real search results rather than generated citations.
Architecture
- 1
Phase 1 is a standard completion — no streaming. Its only job is deciding whether the Advisor needs external data. Cheap, fast, and bounded.
- 2
If the model triggers web_search, Tavily executes and results are normalized into a consistent schema before touching the context.
- 3
Results are injected into the conversation context as a discrete step, not interleaved with generation — this keeps the boundary between retrieval and reasoning visible and debuggable.
- 4
Phase 2 streams against full context: conversation history, user profile, recommendations, roadmap state, and search results if present. The model is never generating against incomplete context.
- 5
When search fails, the failure has a location in the pipeline. It is not invisible inside a long prompt.