activity_analyzer
openai/gpt-4o
Behaviour → digest
Loads the last 60 events, renders them as a chronological log, and asks Mesh for a factual behavioural digest. Weights cart adds and long dwell times far above bare page views.
Architecture
Nexora is a seven-node LangGraph state machine. It reads behavioural events, extracts the interests behind them, retrieves candidates with hybrid vector search, grades each one with an LLM judge, loops back to broaden the search when the result set is thin, and only then writes the recommendation. Every model call is routed through the Mesh API gateway.
openai/gpt-4o
Behaviour → digest
Loads the last 60 events, renders them as a chronological log, and asks Mesh for a factual behavioural digest. Weights cart adds and long dwell times far above bare page views.
openai/gpt-4o
Digest → signals + query + filters
Produces 3–5 interest signals with confidence scores and supporting evidence, a rich natural-language retrieval query, and the metadata filters (skill band, price ceiling) the behaviour actually supports.
openai/text-embedding-3-small
Hybrid search over Qdrant
Embeds the query via Mesh, runs a filtered cosine ANN search in Qdrant, runs an Okapi BM25 keyword ranking over the SQL catalog, and fuses the two rankings with reciprocal rank fusion.
openai/gpt-4o-mini
LLM-as-judge grading + re-ranking
Scores every candidate 0–1 with a one-line justification, then blends the judge score with the retrieval score (65/35) to produce the final ordering. Topical keyword overlap alone does not count as relevance.
openai/gpt-4o
Broaden and retry
Entered only via the conditional edge when fewer than three relevant candidates survive. Moves the query up a level of abstraction and can drop the price or skill filters, then loops back into retrieval.
anthropic/claude-3-5-sonnet
Evidence-grounded narrative
Writes the headline, the narrative and a pitch per course — instructed to reflect the observed behaviour back to the learner, and forbidden from inventing courses, prices or urgency.
no model call
Persist and invalidate
Deactivates previous recommendations and inserts the new one in a single transaction, then invalidates the Redis result cache and clears the pending flag so the UI stops showing a skeleton.
One conditional edge, on relevance_grader: proceed to the
writer when at least 3 relevant candidates survive; otherwise
refine and retry, up to 2 times; if the budget runs out,
write a recommendation from what we have rather than returning nothing.
---
config:
flowchart:
curve: linear
---
graph TD;
__start__([<p>__start__</p>]):::first
activity_analyzer(activity_analyzer)
interest_extractor(interest_extractor)
retrieval_node(retrieval_node)
relevance_grader(relevance_grader)
retrieval_refiner(retrieval_refiner)
persuasion_writer(persuasion_writer)
recommendation_storer(recommendation_storer)
__end__([<p>__end__</p>]):::last
__start__ --> activity_analyzer;
activity_analyzer --> interest_extractor;
interest_extractor --> retrieval_node;
persuasion_writer --> recommendation_storer;
relevance_grader -.-> persuasion_writer;
relevance_grader -.-> retrieval_refiner;
retrieval_node --> relevance_grader;
retrieval_refiner --> retrieval_node;
recommendation_storer --> __end__;
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
Mermaid definition, rendered live from the compiled graph. Machine-readable version: /api/agent/graph
Running on every event would be wasteful; running once per session would go stale. Three rules, checked in priority order:
Dispatch is asynchronous: POST /api/events only evaluates two indexed
COUNT queries, then hands the run to APScheduler. A Redis SET NX EX lock
keyed by user id makes duplicate concurrent runs impossible.
Scheduled jobs
A recommendation engine that returns a 500 is worse than one that returns a slightly weaker answer. Every node catches its own exceptions, records them in the run trace, and falls back to a deterministic heuristic — behavioural weighting for interests, lexical overlap for grading, a templated narrative for copy. The run is flagged degraded rather than lost, which is also what makes the whole pipeline runnable in CI with no API keys. The same principle applies to infrastructure: an unreachable Qdrant falls back to an embedded in-process index, and an unreachable Redis falls back to a process-local cache with identical semantics.
about this build
Nexora was designed and built by AY Systum for the SmartReco Build Challenge 2026 — a hackathon brief to build a behavioural AI recommendation agent that watches, understands and persuades. Every LLM completion and every embedding in this product is routed through the Mesh API gateway; no provider SDK is called directly.