mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-10 23:58:28 +00:00
144 lines
5.8 KiB
TypeScript
144 lines
5.8 KiB
TypeScript
import { metadata, task } from "@trigger.dev/sdk";
|
|
import { streamText, type CoreMessage, tool } from "ai";
|
|
import { z } from "zod";
|
|
|
|
import { openai } from "@ai-sdk/openai";
|
|
import { logger } from "~/services/logger.service";
|
|
import {
|
|
deletePersonalAccessToken,
|
|
getOrCreatePersonalAccessToken,
|
|
} from "../utils/utils";
|
|
import axios from "axios";
|
|
import { nanoid } from "nanoid";
|
|
|
|
export const ExtensionSearchBodyRequest = z.object({
|
|
userInput: z.string().min(1, "User input is required"),
|
|
userId: z.string().min(1, "User ID is required"),
|
|
outputType: z.string().default("markdown"),
|
|
context: z
|
|
.string()
|
|
.optional()
|
|
.describe("Additional context about the user's current work"),
|
|
});
|
|
|
|
// Export a singleton instance
|
|
export const extensionSearch = task({
|
|
id: "extensionSearch",
|
|
maxDuration: 3000,
|
|
run: async (body: z.infer<typeof ExtensionSearchBodyRequest>) => {
|
|
const { userInput, userId, context } =
|
|
ExtensionSearchBodyRequest.parse(body);
|
|
const outputType = body.outputType;
|
|
const randomKeyName = `extensionSearch_${nanoid(10)}`;
|
|
|
|
const pat = await getOrCreatePersonalAccessToken({
|
|
name: randomKeyName,
|
|
userId: userId as string,
|
|
});
|
|
|
|
// Define the searchMemory tool that actually calls the search service
|
|
const searchMemoryTool = tool({
|
|
description:
|
|
"Search the user's memory for relevant facts and episodes based on a query",
|
|
parameters: z.object({
|
|
query: z.string().describe("Search query to find relevant information"),
|
|
}),
|
|
execute: async ({ query }) => {
|
|
try {
|
|
const response = await axios.post(
|
|
`https://core.heysol.ai/api/v1/search`,
|
|
{ query },
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${pat.token}`,
|
|
},
|
|
},
|
|
);
|
|
const searchResult = response.data;
|
|
|
|
return {
|
|
facts: searchResult.facts || {},
|
|
episodes: searchResult.episodes || [],
|
|
};
|
|
} catch (error) {
|
|
logger.error(`SearchMemory tool error: ${error}`);
|
|
return {
|
|
facts: [],
|
|
episodes: [],
|
|
};
|
|
}
|
|
},
|
|
});
|
|
|
|
const messages: CoreMessage[] = [
|
|
{
|
|
role: "system",
|
|
content: `You are a specialized memory search and summarization agent. Your job is to:
|
|
|
|
1. FIRST: Understand the user's intent and what information they need to achieve their goal
|
|
2. THEN: Design a strategic search plan to gather that information from memory
|
|
3. Execute multiple targeted searches using the searchMemory tool
|
|
4. Format your response in ${outputType} and return exact content from episodes or facts without modification.
|
|
|
|
SEARCH STRATEGY:
|
|
- Analyze the user's query to understand their underlying intent and information needs
|
|
- For comparisons: search each entity separately, then look for comparative information
|
|
- For "how to" questions: search for procedures, examples, and related concepts
|
|
- For troubleshooting: search for error messages, solutions, and similar issues
|
|
- For explanations: search for definitions, examples, and context
|
|
- Always use multiple targeted searches with different angles rather than one broad search
|
|
- Think about what background knowledge would help answer the user's question
|
|
|
|
EXAMPLES:
|
|
- "Graphiti vs CORE comparison" → Intent: Compare two systems → Search: "Graphiti", "CORE", "Graphiti features", "CORE features"
|
|
- "How to implement authentication" → Intent: Learn implementation → Search: "authentication", "authentication implementation", "login system"
|
|
- "Why is my build failing" → Intent: Debug issue → Search: "build error", "build failure", "deployment issues"
|
|
|
|
IMPORTANT: Always format your response in ${outputType}. When you find relevant content in episodes or facts, return the exact content as found - preserve lists, code blocks, formatting, and structure exactly as they appear. Present the information clearly organized in ${outputType} format with appropriate headers and structure.
|
|
|
|
HANDLING PARTIAL RESULTS:
|
|
- If you find complete information for the query, present it organized by topic
|
|
- If you find partial information, clearly state what you found and what you didn't find
|
|
- Always provide helpful related information even if it doesn't directly answer the query
|
|
- Example: "I didn't find specific information about X vs Y comparison, but here's what I found about X: [exact content] and about Y: [exact content], which can help you build the comparison"
|
|
|
|
If no relevant information is found at all, provide a brief statement indicating that in ${outputType} format.`,
|
|
},
|
|
{
|
|
role: "user",
|
|
content: `User input: "${userInput}"${context ? `\n\nAdditional context: ${context}` : ""}\n\nPlease search my memory for relevant information and provide the exact content from episodes or facts that relate to this question. Format your response in ${outputType} and do not modify or summarize the found content.`,
|
|
},
|
|
];
|
|
|
|
try {
|
|
const result = streamText({
|
|
model: openai(process.env.MODEL as string),
|
|
messages,
|
|
tools: {
|
|
searchMemory: searchMemoryTool,
|
|
},
|
|
maxSteps: 5,
|
|
temperature: 0.3,
|
|
maxTokens: 1000,
|
|
});
|
|
|
|
const stream = await metadata.stream("messages", result.textStream);
|
|
|
|
let finalText: string = "";
|
|
for await (const chunk of stream) {
|
|
finalText = finalText + chunk;
|
|
}
|
|
|
|
await deletePersonalAccessToken(pat?.id);
|
|
|
|
return finalText;
|
|
} catch (error) {
|
|
await deletePersonalAccessToken(pat?.id);
|
|
|
|
logger.error(`SearchMemoryAgent error: ${error}`);
|
|
|
|
return `Context related to: ${userInput}. Looking for relevant background information, previous discussions, and related concepts that would help provide a comprehensive answer.`;
|
|
}
|
|
},
|
|
});
|