Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 34 additions & 19 deletions app/agents/linkedin.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import { AgentRequest, AgentResponse } from './types';
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
import { AgentRequest, AgentResponse } from "./types";
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";

export async function linkedInAgent(
request: AgentRequest
request: AgentRequest,
): Promise<AgentResponse> {
// TODO: Step 1 - Get the fine-tuned model ID
// Access process.env.OPENAI_FINETUNED_MODEL
// If not configured, you might want to throw an error or use a fallback
// TODO: Step 1 - Get the fine-tuned model ID
// Access process.env.OPENAI_FINETUNED_MODEL
// If not configured, you might want to throw an error or use a fallback
const modelId = process.env.OPENAI_FINETUNED_MODEL;
if (!modelId) throw new Error("couldn't get model id");

// TODO: Step 2 - Build the system prompt
// Include instructions for the LinkedIn agent
// Add context about the original and refined queries:
// - request.originalQuery - what the user originally asked
// - request.query - the refined/improved version
// Tell the model to create engaging LinkedIn posts
// TODO: Step 2 - Build the system prompt
// Include instructions for the LinkedIn agent
// Add context about the original and refined queries:
// - request.originalQuery - what the user originally asked
// - request.query - the refined/improved version
// Tell the model to create engaging LinkedIn posts
const systemPrompt = `You are a LinkedIn agent. Analyze the users refined query and create a unique Linkedin post.

// TODO: Step 3 - Stream the response
// Use streamText() from the 'ai' package
// Pass the model using openai()
// Include system prompt and messages from request.messages
// Return the stream
Your task:
1. analyze the query
2. identify the topic for the post
3. create a unique post for the topic
4. return the new post.`;
// TODO: Step 3 - Stream the response
// Use streamText() from the 'ai' package
// Pass the model using openai()
// Include system prompt and messages from request.messages
// Return the stream

throw new Error('LinkedIn agent not implemented yet!');
return streamText({
model: openai(modelId),
system: systemPrompt,
prompt: `
Original User Request: ${request.originalQuery}
Refined Query: ${request.query}
`,
});
}
148 changes: 97 additions & 51 deletions app/agents/rag.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,100 @@
import { AgentRequest, AgentResponse } from './types';
import { pineconeClient } from '@/app/libs/pinecone';
import { openaiClient } from '@/app/libs/openai/openai';
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
import { AgentRequest, AgentResponse } from "./types";
import { pineconeClient } from "@/app/libs/pinecone";
import { openaiClient } from "@/app/libs/openai/openai";
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";

export async function ragAgent(request: AgentRequest): Promise<AgentResponse> {
// TODO: Step 1 - Generate embedding for the refined query
// Use openaiClient.embeddings.create()
// Model: 'text-embedding-3-small'
// Dimensions: 512
// Input: request.query
// Extract the embedding from response.data[0].embedding

// TODO: Step 2 - Query Pinecone for similar documents
// Get the index: pineconeClient.Index(process.env.PINECONE_INDEX!)
// Query parameters:
// - vector: the embedding from step 1
// - topK: 10 (to over-fetch for reranking)
// - includeMetadata: true

// TODO: Step 3 - Extract text from results
// Map over queryResponse.matches
// Get metadata?.text (or metadata?.content as fallback)
// Filter out any null/undefined values

// TODO: Step 4 - Rerank with Pinecone inference API
// Use pineconeClient.inference.rerank()
// Model: 'bge-reranker-v2-m3'
// Pass the query and documents array
// This gives you better quality results

// TODO: Step 5 - Build context from reranked results
// Map over reranked.data
// Extract result.document?.text from each
// Join with '\n\n' separator

// TODO: Step 6 - Create system prompt
// Include:
// - Instructions to answer based on context
// - Original query (request.originalQuery)
// - Refined query (request.query)
// - The retrieved context
// - Instruction to say if context is insufficient

// TODO: Step 7 - Stream the response
// Use streamText()
// Model: openai('gpt-4o')
// System: your system prompt
// Messages: request.messages
// Return the stream

throw new Error('RAG agent not implemented yet!');
// TODO: Step 1 - Generate embedding for the refined query
// Use openaiClient.embeddings.create()
// Model: 'text-embedding-3-small'
// Dimensions: 512
// Input: request.query
// Extract the embedding from response.data[0].embedding
const response = await openaiClient.embeddings.create({
model: "text-embedding-3-small",
dimensions: 512,
input: request.query,
});
const embedding = response.data[0].embedding;
// TODO: Step 2 - Query Pinecone for similar documents
// Get the index: pineconeClient.Index(process.env.PINECONE_INDEX!)
// Query parameters:
// - vector: the embedding from step 1
// - topK: 10 (to over-fetch for reranking)
// - includeMetadata: true
const index = pineconeClient.Index(process.env.PINECONE_INDEX!);
const result = await index.query({
vector: embedding,
topK: 10,
includeMetadata: true,
});

// TODO: Step 3 - Extract text from results
// Map over queryResponse.matches
// Get metadata?.text (or metadata?.content as fallback)
// Filter out any null/undefined values
const text = result.matches
.map((res) => res.metadata?.text)
.filter((text) => typeof text === "string");

// TODO: Step 4 - Rerank with Pinecone inference API
// Use pineconeClient.inference.rerank()
// Model: 'bge-reranker-v2-m3'
// Pass the query and documents array
// This gives you better quality results
const reranked = await pineconeClient.inference.rerank(
"bge-reranker-v2-m3",
request.query,
text,
{ topN: 5, returnDocuments: true },
);

// TODO: Step 5 - Build context from reranked results
// Map over reranked.data
// Extract result.document?.text from each
// Join with '\n\n' separator
const retrievedContext = reranked.data
.map((result) => result.document?.text)
.filter(Boolean)
.join("\n\n");

// TODO: Step 6 - Create system prompt
// Include:
// - Instructions to answer based on context
// - Original query (request.originalQuery)
// - Refined query (request.query)
// - The retrieved context
// - Instruction to say if context is insufficient
const systemPrompt = `You are a helpful assistant answering questions based on the provided context in the user's query.

use the context in the query to answer the user's question.

original query: ${request.originalQuery}
refined query: ${request.query}
retrieved context: ${retrievedContext}

if the context doesnt contain enough information, say so clearly or do your best to answer the question`;

// TODO: Step 7 - Stream the response
// Use streamText()
// Model: openai('gpt-4o')
// System: your system prompt
// Messages: request.messages
// Return the stream
console.log(
"Pinecone scores:",
result.matches.map((m) => ({ score: m.score, text: m.metadata?.text })),
);
console.log(
"Re-ranked scores:",
reranked.data.map((r) => ({ score: r.score, text: r.document })),
);
console.log("Context length:", retrievedContext.length);

return streamText({
model: openai("gpt-4o"),
system: systemPrompt,
prompt: `Context: ${retrievedContext}\n\nQuery: ${request.query}`,
});
}
48 changes: 30 additions & 18 deletions app/api/rag-test/route.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import { searchDocuments } from '@/app/libs/pinecone';
import { NextRequest, NextResponse } from 'next/server';
import { searchDocuments } from "@/app/libs/pinecone";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
const body = await request.json();
const { query, topK } = body;
try {
const body = await request.json();
const { query, topK = 5 } = body;

const results = await searchDocuments(query, topK);
if (!query || typeof query !== "string") {
console.error("Query is required for RAG search");
return NextResponse.json({ error: "Query is required" }, { status: 400 });
}
const results = await searchDocuments(query, topK);

const formattedResults = results.map((doc) => ({
id: doc.id,
score: doc.score,
content: doc.metadata?.text || '',
source: doc.metadata?.source || 'unknown',
chunkIndex: doc.metadata?.chunkIndex,
totalChunks: doc.metadata?.totalChunks,
}));
const formattedResults = results.map((doc) => ({
id: doc.id,
score: doc.score,
content: doc.metadata?.text || "",
source: doc.metadata?.source || "unknown",
chunkIndex: doc.metadata?.chunkIndex,
totalChunks: doc.metadata?.totalChunks,
}));

return NextResponse.json({
query,
resultsCount: formattedResults.length,
results: formattedResults,
});
return NextResponse.json({
query,
resultsCount: formattedResults.length,
results: formattedResults,
});
} catch (error) {
console.error("❌ Error in RAG test route:", error);
return NextResponse.json(
{ error: "Failed to perform RAG search" },
{ status: 500 },
);
}
}
Loading