Assignment #14 Β· Agentic AI Β· NestJS Β· TypeScript Β· Enterprise Grade
A token-optimised, low-latency, Qdrant-backed, LangGraph-orchestrated multi-agent system.
- System Overview & PRD
- Architecture & Agentic Flow (LangGraph.js)
- Tech Stack & Modular Architecture
- Resilient Data Access Layer (Prisma & Postgres Fallback)
- Caching Architecture (L1, L2, L3 Caches)
- Queueing & Async Search Orchestration (BullMQ)
- Qdrant Vector Database Design
- Token Economics & Custom Compression (RTK Pattern)
- Latency Budget & Optimization Matrix
- Data Flows (End-to-End Visualizations)
- Real-Time Streaming & Server-Sent Events (SSE)
- Observability, Tracing & Evals
- Implementation Checklist
- Team Members & Contributions
- Local Environment Setup
The Agentic Travel Planning System is a production-grade multi-agent backend built on NestJS and orchestrated via LangGraph.js. The system accepts a single natural-language travel brief (e.g., "3-day trip to Tokyo next month, βΉ2L budget, prefer center stay and cultural activities") and autonomously parses the intent, triggers parallel external search engines, constructs a conflict-free itinerary, and manages post-booking disruptions like cancellations or delays.
- Natural-Language Intake: Parses complex user inputs into typed, structured constraints (
TravelConstraints). - Parallel Search Execution: Concurrently queries flight, hotel, and activity sources to prevent long HTTP blockings.
- Dynamic Itinerary Assembly: Synthesizes day-by-day itineraries that match budget, timing, and personal constraints.
- Automated Conflict Resolution: Evaluates timing overlaps, hotel gaps, tight connections, and budget overflows, executing programmatically or via reasoning models.
- Downstream Change Propagation: Evaluates disruptions (e.g., flight delay), marks affected downstream segments (like hotel check-ins or scheduled tours), and calculates a revised itinerary.
- Real-Time Progress Streaming: Streams the planner's reasoning steps, search results, and assembled segments via Server-Sent Events (SSE).
- High Token Efficiency: Targets <10% token cost compared to naive LLM agent loops by applying context compressors, prefix caches, and segment-level difference-tracking.
The agentic core is structured as a state machine using @langchain/langgraph's StateGraph compiled into a stateful runner (TravelGraphService).
graph TD
UserBrief[User Travel Brief] --> Routing{Route Request}
Routing -- New Trip --> IntentParser[Intent Parser Agent<br>Model: Claude Haiku]
Routing -- Change Request --> ChangeManager[Change Manager Agent<br>Model: Claude Sonnet]
IntentParser --> SearchOrchestrator[Search Orchestrator Node<br>Parallel API + Vector Queries]
SearchOrchestrator --> ItineraryAssembler[Itinerary Assembler Agent<br>Model: Claude Haiku]
ItineraryAssembler --> ConflictResolver[Conflict Resolver Agent<br>Step 1: TypeScript Engine<br>Step 2: Claude Sonnet Fallback]
ChangeManager --> ConflictResolver
ConflictResolver --> LoopCheck{All Resolved or<br>Max 5 Iterations?}
LoopCheck -- No --> ConflictResolver
LoopCheck -- Yes --> Responder[Responder Agent / SSE Stream]
Responder --> DB[(Postgres + Qdrant)]
The graph uses a shared context defined in travel-state.ts with explicit channel types and custom reducer functions:
export const StateAnnotation = Annotation.Root({
sessionId: Annotation<string>,
tripId: Annotation<string>,
userId: Annotation<string>,
rawBrief: Annotation<string>,
// Overwrites on updates
parsedBrief: Annotation<TravelBrief | null>({
reducer: (left, right) => (right !== undefined ? right : left),
default: () => null,
}),
// Overwrites on updates
flightOptions: Annotation<Flight[]>({
reducer: (left, right) => right || [],
default: () => [],
}),
hotelOptions: Annotation<Hotel[]>({
reducer: (left, right) => right || [],
default: () => [],
}),
activityOptions: Annotation<Activity[]>({
reducer: (left, right) => right || [],
default: () => [],
}),
itinerary: Annotation<Itinerary | null>({
reducer: (left, right) => (right !== undefined ? right : left),
default: () => null,
}),
conflicts: Annotation<Conflict[]>({
reducer: (left, right) => right || [],
default: () => [],
}),
// Accumulate list items (Append-only reducer logic)
resolvedConflicts: Annotation<Resolution[]>({
reducer: (left, right) => left.concat(right || []),
default: () => [],
}),
changeRequest: Annotation<ChangeRequest | null>({
reducer: (left, right) => (right !== undefined ? right : left),
default: () => null,
}),
affectedSegmentIds: Annotation<string[]>({
reducer: (left, right) => right || [],
default: () => [],
}),
// Logs append-only
thoughtLog: Annotation<ThoughtEntry[]>({
reducer: (left, right) => left.concat(right || []),
default: () => [],
}),
toolCallLog: Annotation<ToolCallEntry[]>({
reducer: (left, right) => left.concat(right || []),
default: () => [],
}),
errors: Annotation<string[]>({
reducer: (left, right) => left.concat(right || []),
default: () => [],
}),
status: Annotation<
| "parsing"
| "searching"
| "assembling"
| "resolving"
| "changing"
| "done"
| "error"
>({
reducer: (left, right) => right || left,
default: () => "parsing",
}),
compressedContext: Annotation<string | undefined>({
reducer: (left, right) => (right !== undefined ? right : left),
default: () => undefined,
}),
});- Conditional Start Edge: Checks the incoming state payload. If
changeRequestis present, it routes immediately tochange_manager; otherwise, it passes totemplate_fast_pathfor caching checks. template_fast_pathNode: Queries Qdrant for past similar itineraries. If a template match is found, it skips the cold search completely, populatesitinerary, and routes straight to theconflict_resolvernode.intent_parserNode: Uses Claude 3.5 Haiku to extract typed JSON constraints (TravelConstraints) from natural language.search_orchestratorNode: Dispatches parallel searches through flights, hotels, and activities using BullMQ or synchronous fallbacks, caching responses under Redis and Qdrant.itinerary_assemblerNode: Employs Claude 3.5 Haiku to construct a day-by-day itinerary sequence from the compressed search results.conflict_resolverNode: Runs rule-based validation on the state. If conflicts are detected, it invokes Claude 3.5 Sonnet to perform resolution actions (such as reordering or rebooking segments) up to 5 iterations.change_managerNode: Runs Claude 3.5 Sonnet to perform analysis on incoming disruption events (e.g. flight cancellations), calling tools likehandle_flight_changeandpropagate_downstream.responderNode: Saves final itineraries as templates to Qdrant, updates the relational databases, and broadcasts completion streams via SSE.
travel-agent/
βββ src/
β βββ common/
β β βββ mock/ # In-memory mock suppliers (Amadeus, Booking, Qdrant fallbacks)
β β βββ types/ # Domain contracts (travel.types.ts, agent.types.ts, search.types.ts)
β βββ modules/
β β βββ agent/ # LangGraph orchestrator, node controllers & agent tools
β β βββ search/ # External API client gateways (Amadeus API, Booking API)
β β βββ memory/ # Vector embeddings (Voyage AI / Gemini) and Qdrant clients
β β βββ cache/ # Redis caching service & BullMQ job orchestration
β β βββ llm/ # Resilient LLM wrappers and token tracking service
β β βββ trips/ # HTTP controller routing and Prisma ORM repository
- NestJS Modular Core: Enforces dependency separation, dependency injection, and centralized routing configurations.
- LangGraph.js Orchestration: Replaces fragile conditional loops with a compiled directed state graph, supporting loop checks and strict schema isolation.
- Qdrant Vector DB: Dual dense-sparse semantic memory, hybrid searches, and template fast-path indexing.
- Redis + BullMQ: Out-of-process job queuing, workers, and L1 cache management.
- PostgreSQL + Prisma: Persistent storage for transactions, itinerary logging, and session telemetry.
- Langfuse tracing: Tracks token usage by category (prefix, tool, API data, session history) and latency metrics.
To achieve high availability, TripsRepository implements a fail-safe memory fallback pattern that keeps the application fully functional even if PostgreSQL goes offline.
The database is mapped via Prisma (schema.prisma) to persist trips, sessions, and change events:
model Trip {
id String @id @default(uuid())
userId String
status TripStatus @default(PLANNING)
rawBrief String
parsedBrief Json?
itinerary Json?
budgetSummary Json?
conflicts Json[]
changeLog Json[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model AgentSession {
id String @id @default(uuid())
tripId String
status String // running, completed, failed
checkpoints Json[]
thoughtLog Json[]
toolCallLog Json[]
rtkSavings Json?
createdAt DateTime @default(now())
}If the PostgreSQL server experiences a connection crash, the TripsRepository intercepts the error and flags useFallback = true. It seamlessly transitions database transactions to local memory maps (memoryTrips, memorySessions), preventing 500 errors from reaching the end-user:
@Injectable()
export class TripsRepository implements ITripsRepository {
private readonly logger = new Logger(TripsRepository.name);
private readonly memoryTrips = new Map<string, Trip>();
private readonly memorySessions = new Map<string, AgentSession>();
private useFallback = false;
constructor(private readonly prisma: PrismaService) {}
private checkDbConnection(): boolean {
return !this.useFallback;
}
async createTrip(userId: string, rawBrief: string): Promise<Trip> {
if (this.checkDbConnection()) {
try {
return await this.prisma.trip.create({
data: {
userId,
rawBrief,
status: TripStatus.PLANNING,
conflicts: [],
changeLog: [],
},
});
} catch (error) {
this.logger.error(
"PostgreSQL error. Activating local in-memory fallback.",
error,
);
this.useFallback = true;
}
}
// Local Fallback Allocation
const fallbackTrip: Trip = {
id: `fallback-trip-${Math.random().toString(36).substring(2, 11)}`,
userId,
status: TripStatus.PLANNING,
rawBrief,
parsedBrief: null,
itinerary: null,
budgetSummary: null,
conflicts: [],
changeLog: [],
createdAt: new Date(),
updatedAt: new Date(),
};
this.memoryTrips.set(fallbackTrip.id, fallbackTrip);
return fallbackTrip;
}
// Implements identical fallback structures for getTrip, updateTrip, createSession, and updateSession...
}The system is optimized through a multi-tiered caching architecture that prevents redundant network traffic and LLM calls.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β L1 Cache (Redis / Local Map): Exact query hits (~0.1s) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β L2 Cache (Qdrant): Semantic Cache on Query Embedding (~0.3s)β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β L3 Cache (Qdrant): Template Matching and Hot-Patching (~1s) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Managed by RedisService utilizing the custom helper getOrSearch<T>(key, ttlSeconds, fallback):
- Connection Resilience: Connects using a custom
retryStrategyup to 3 times before settinguseFallback = trueand falling back to a localmemoryCacheMap. - Parameter: Configured with
maxRetriesPerRequest: 3andretryStrategy. - Execution flow:
async getOrSearch<T>(key: string, ttlSeconds: number, fallback: () => Promise<T>): Promise<T> { const cached = await this.get(key); if (cached) { try { return JSON.parse(cached) as T; } catch {} } const result = await fallback(); await this.setex(key, ttlSeconds, JSON.stringify(result)); return result; }
Queries are converted into embeddings (Voyage/Gemini) and checked against the Qdrant semantic_query_cache collection:
- Distance Metric: Uses Cosine distance.
-
Match Policy: If the incoming query embedding is within a Cosine distance threshold
$\leq 0.03$ of a cached query, the search stage directly returns the cached results, bypassing the external API call entirely.
When starting a new session, the system queries the itinerary_templates collection in Qdrant using the brief's embedding:
-
Match Policy: If a similar past itinerary has a similarity score
$\geq 0.92$ , the graph loads it into the graph state. - Execution: Rather than running flight, hotel, and activity queries from scratch, the system bypasses the search stage and directly performs hot-patching of dates, prices, and bookings.
To handle parallel search tasks without blocking the main NestJS HTTP thread, search operations are dispatched as jobs to a Redis-backed BullMQ queue.
ββββββββββββββββββββββββ
β SearchOrchestrator β
ββββββββββββ¬ββββββββββββ
β (Promise.allSettled)
βββββββββββββββββββββΌββββββββββββββββββββ
βΌ βΌ βΌ
ββββββββββββ ββββββββββββ ββββββββββββ
β Flight β β Hotel β β Activity β
β Search β β Search β β Search β
ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ
β β β
βββββββββββββββββββββΌββββββββββββββββββββ
β
βΌ (Gathers and compresses)
ββββββββββββββββββββββββ
β Itinerary Assembler β
ββββββββββββββββββββββββ
- Attempts: Retries failed jobs up to 3 times.
- Backoff Strategy: Exponential backoff with a delay of
1000ms. - Worker Configuration: A dedicated worker (
Worker) is registered under the"agent-tasks"queue name to run the specialized tool routines (search:flights,search:hotels,search:activities).
If Redis is down, QueueService transitions to useFallback = true. Jobs are executed synchronously using memory-backed listeners, running in the background (handler(data).catch(...)) to prevent request cycle blockings:
async addJob<T>(name: string, data: T): Promise<string> {
if (!this.useFallback && this.queue) {
try {
const job = await this.queue.add(name, data);
return job.id || "queued";
} catch (err) {
this.logger.warn(`Redis queue fail. Falling back to synchronous execution.`);
this.useFallback = true;
}
}
// Resilient Synchronous Fallback
const handler = this.registeredHandlers.get(name);
if (handler) {
handler(data).catch((err) => this.logger.error(`Synchronous fallback execution for [${name}] failed:`, err));
return "sync-fallback-executed";
}
throw new Error(`Failed to process job: No handler registered for task type: ${name}`);
}Qdrant handles dense-sparse semantic queries, caching, and template matching.
On server startup, QdrantService auto-initializes the required collections:
hotels: Stores hotel descriptions and locations.activities: Stores city-wide attractions, dining, and schedules.itinerary_templates: Caches assembled itineraries.semantic_query_cache: Caches search inputs and compressed results.traveller_preferences: Persists preferences parsed from briefs.
Each collection is initialized with standard configurations:
await this.client.createCollection(name, {
vectors: { size: 1536, distance: "Cosine" },
});If Qdrant goes offline during local development or staging, the QdrantService automatically falls back to an in-memory Map-backed store, computing cosine similarity programmatically in TypeScript:
private cosineSimilarity(vecA: number[], vecB: number[]): number {
if (vecA.length !== vecB.length) return 0;
let dot = 0, nA = 0, nB = 0;
for (let i = 0; i < vecA.length; i++) {
dot += vecA[i] * vecB[i];
nA += vecA[i] * vecA[i];
nB += vecB[i] * vecB[i];
}
return nA === 0 || nB === 0 ? 0 : dot / (Math.sqrt(nA) * Math.sqrt(nB));
}Every byte that crosses the prompt boundary increases latency and api charges. We apply a 5-layer stack to keep tokens within defined limits.
- Measurement (Layer 0): Every LLM call records telemetry through
TokenTrackerService(categorizing tokens into Prefix, Compressed APIs, Session State, User Request, and History). - Stable Prefix Caching (Layer 1): Embeds tool schemas, domain rules, and system instructions at the beginning of system prompts. This keeps prompt headers unchanged between turns, triggering model provider cache hits.
- API Response Compression (Layer 2 - RTK Pattern): Raw API outputs are stripped of bloated keys (like links, raw metadata, and duplicate IDs).
- Itinerary Deltas (Layer 3): During edit operations, the system does not submit the entire day-by-day plan. Instead,
DeltaTrackerServicecalculates changed segments, sending only modified nodes. - Sliding Session Window (Layer 4): Truncates historical conversation turns to include only the last 3 turns.
The system strips raw API responses before feeding them to the prompt context. This is achieved by executing the local RTK binary (rtk cat shell command) or falling back to the TypeScript compressor:
-
Amadeus flight search: Cleans raw flights, slices to top 5 options, extracts validating airline, departure/arrival IATA codes, and converts prices to local currencies.
-
Raw size: ~48 KB
$\rightarrow$ Compressed size: ~800 bytes (98.3% compression rate)
-
Raw size: ~48 KB
-
Booking.com hotel search: Truncates descriptions, extracts stars, addresses, prices, and coordinates.
-
Raw size: ~30 KB
$\rightarrow$ Compressed size: ~600 bytes (98.0% compression rate)
-
Raw size: ~30 KB
-
Activities search: Standardizes attraction category, price levels, and editorial summaries.
-
Raw size: ~15 KB
$\rightarrow$ Compressed size: ~400 bytes (97.3% compression rate)
-
Raw size: ~15 KB
The system tracks latency across graph node executions to ensure responsive client updates.
| Graph Operation | Unoptimized Baseline | Target Budget | Primary Lever |
|---|---|---|---|
| Intent Parsing | 3.5s | 0.8s | Claude 3.5 Haiku + structured JSON formatting |
| Search Queries | 18.0s (Sequential API) | 4.0s | Concurrency via Promise.allSettled() |
| Itinerary Assembly | 12.0s | 3.0s | Pre-compressed inputs + prompt prefix caching |
| Conflict Detection | 4.5s (LLM-based) | 0.5s | Rule-based TypeScript checks |
| Downstream Re-plan | 10.0s | 3.0s | Segment-level deltas + sliding history window |
| L1/L2 Cache Hit | β | 0.1s - 0.3s | Redis + Qdrant Semantic Query Cache |
| L3 Template Hit | β | 1.2s | Qdrant vector template retrieval & patching |
[User Input] ββ> TripsController ββ> state.changeRequest ? NO
β
βΌ
[Intent Parser Agent] ββ> TravelBrief (JSON)
β
βΌ
[Search Orchestrator] ββ> Parallel API Search (Flights, Hotels, Activities)
β
βΌ
[Context Compressor] ββ> Compressed Strings (~1.5KB total)
β
βΌ
[Itinerary Assembler] ββ> Compiles Days & Segments
β
βΌ
[Conflict Detector] ββ> TypeScript rules check ββ> Clear
β
βΌ
[TripsRepository] ββ> Saved to DB ββ> Streamed to Client
[Disruption Notification] ββ> TripsController (changeRequest: Flight Cancelled)
β
βΌ
[Change Manager Agent]
β
(Analyses downstream impact:
hotel check-in & day 1 activities)
β
βΌ
[handle_flight_change.tool]
(Flags flight as cancelled in state)
β
βΌ
[propagate_downstream.tool]
(Triggers search for new flights)
β
βΌ
[Conflict Resolver]
(Overwrites dates, adjusts hotel,
shifts/re-schedules day-1 events)
β
βΌ
[DB Update & SSE] ββ> User approves new plan
The system streams progress updates to the client using a real-time event pipeline.
βββββββββββββββββββ βββββββββββββββ βββββββββββββββββ
β LangGraph Node ββββββββ>β SseService ββββββββ>β SseController β
β (calls emit) β β (Event Bus) β β (RxJS stream) β
βββββββββββββββββββ βββββββββββββββ βββββββββ¬ββββββββ
β
βΌ (HTTP GET Stream)
Client Interface
Updates are pushed to the client using structured event payloads:
graph:node_start: Emitted when a node begins executing (e.g.intent_parser,search_orchestrator).graph:search_complete: Broadcasts the quantity of search results retrieved.graph:day_assembled: Streams daily itineraries as they are assembled.graph:conflict_detected: Emits warning payloads describing detected conflicts.graph:conflict_resolved: Emits details of conflict resolutions, including the applied changes and explanations.graph:complete: Closes the stream once the itinerary is successfully resolved and saved.
// Example: graph:conflict_detected
{
"type": "graph:conflict_detected",
"sessionId": "b0f7dfaa-c322-4886-8ad3-dcf7cc5e8c11",
"timestamp": "2026-06-18T11:45:12.103Z",
"payload": {
"id": "conflict-1",
"conflictType": "CHECK_IN_BEFORE_LANDING",
"severity": "critical",
"description": "Hotel check-in is scheduled for 15:00, but flight lands at 17:45.",
"affectedItems": ["hotel-1", "flight-1"]
}
}Every graph execution is recorded in the logs, breaking down token usage by category:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TOKEN TRACKER OBSERVABILITY REPORT β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Session: b0f7dfaa-c322-4886-8ad3-dcf7cc5e8c11 β
β Node: itinerary_assembler β
β Model: claude-3-5-haiku β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β INPUT TOKENS: β
β ββ Stable Prefix (cached): 4320 β
β ββ Compressed APIs (RTK): 750 β
β ββ Session State: 180 β
β ββ User Request: 45 β
β ββ History Window: 0 β
β TOTAL INPUT: 5295 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β OUTPUT TOKENS: β
β ββ Reasoning/Text: 210 β
β ββ Tool Calls: 120 β
β TOTAL OUTPUT: 330 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β PERFORMANCE & RTK SAVINGS: β
β ββ Latency (ms): 1230 β
β ββ Est. Tokens Saved: 28900 β
β ββ Savings Rate (%): 85% β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-
constraint-extraction.eval.spec.ts: Validates that the parser extracts travel criteria from user briefs. -
conflict-detection.eval.spec.ts: Tests that the validation rules flag invalid itineraries (e.g. check-in timing conflicts). -
change-propagation.eval.spec.ts: Evaluates delay and cancellation propagation rules. -
token-budget.eval.spec.ts: Asserts that token consumption remains within defined limits (e.g. intent parsing$< 2,000$ tokens, assembly$< 4,000$ tokens).
- LangGraph.js compiled StateGraph layout and node controllers.
- Dual-engine conflict detector (TypeScript rules + Sonnet reasoning).
- Context compressor with custom API cleaners (RTK equivalent).
- Multi-tier caching structures (Redis cache + Qdrant semantic query index).
- BullMQ parallel task queues.
- Fail-safe in-memory fallbacks for databases, caching, and queues.
- Client streaming support via Server-Sent Events (SSE).
- Tracing and logging via
TokenTrackerService. - Evaluation suites (
constraint-extraction,conflict-detection,change-propagation, andtoken-budget).
-
Arhan Das (Lead Agentic Engineer) β
arhan.24bcs10023@sst.scaler.com- Designed the LangGraph state machine, nodes, and router logic.
- Implemented context management, delta tracking, and compression services.
- Built the rule-based conflict detection engine and downstream change propagation logic.
- Set up the SSE notification service, semantic caching, and template fast-path.
- Developed the testing suites (
constraint-extraction,conflict-detection,change-propagation, andtoken-budget).
-
Aashu Kumar (Backend & Infrastructure Architect) β
aashu.24bcs10172@sst.scaler.com- Designed the data models and early system specifications (
agent.md). - Implemented the database repository layer with fallback mechanisms.
- Integrated the BullMQ queue architecture for processing parallel tasks.
- Co-authored search tools and external API client integrations.
- Designed the data models and early system specifications (
-
Choudhary Khushboo Girdhareeram (Documentation & DevOps Lead) β
choudhary.24bcs10142@sst.scaler.com- Authored the project documentation, system diagrams, and structural specs.
- Managed repository configuration, environment variables, and Docker Compose configurations.
- Conducted test audits and verified project specifications.
-
Adhyayan Gupta (Quality Assurance & Validation) β
adhyayan.24bcs10055@sst.scaler.com- Designed mock data configurations and edge-case brief profiles.
- Verified API responses and schema mappings.
- Tested conflict-resolution scenarios and verified SSE stream outputs on the frontend client.
- Node.js v22.x LTS
- Docker and Docker Compose
- npm
- Anthropic and Google AI Gemini API keys
-
Configure Environment Variables:
cp .env.example .env # Set your ANTHROPIC_API_KEY and GEMINI_API_KEY -
Start Infrastructure Services:
# Starts Postgres, Redis, and Qdrant in detached mode docker compose -f docker/docker-compose.yml up -d -
Install Project Dependencies:
npm install
-
Initialize PostgreSQL Database Schema:
npx prisma db push
-
Seed Qdrant Vector Databases:
npx ts-node scripts/seed-qdrant.ts
-
Start NestJS Development Server:
npm run start:dev
-
Run Tests & Evals:
# Run local unit tests npm run test # Run evaluation suites npm run test:eval
Built with β€οΈ by Team #14.