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
22 changes: 2 additions & 20 deletions apps/web/src/app/[hostingId]/(defaultLayout)/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ export default function SearchPage() {
handleExampleClick,
} = useSearch();

const allData = data ? data.chunks : null;

if (!searchEnabled) {
notFound();
}
Expand Down Expand Up @@ -93,31 +91,15 @@ export default function SearchPage() {
</div>
) : (
<div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
>
<p className="text-sm font-medium">Queries performed:</p>
<p className="text-muted-foreground mt-1 text-xs">
{data.queries.map((q, idx) => (
<i key={idx}>
{q.query}
{idx !== data.queries.length - 1 ? ", " : ""}
</i>
))}
</p>
</motion.div>

<motion.div
className="mt-6 flex w-full flex-col gap-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ delay: 0.05 * 1 }}
>
{allData!.length > 0 ? (
allData!.map((result) => (
{data.length > 0 ? (
data.map((result) => (
<SearchChunk
key={result.id}
chunk={result}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ export const useSearch = () => {
| { success: false }
| {
success: true;
data: {
totalQueries: number;
queries: { type: "keyword" | "semantic"; query: string }[];
chunks: QueryVectorStoreResult["results"];
};
data: QueryVectorStoreResult["results"];
};

if (!data.success) {
Expand Down
40 changes: 0 additions & 40 deletions apps/web/src/app/api/(internal-api)/benchmark/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,6 @@ but it is not as concise."
}
`;

// export const FAITHFULNESS_REFINE_SYSTEM_PROMPT = prmpt`
// We want to understand if the following information is present
// in the context information: ${"query"}
// We have provided an existing answer: ${"existingAnswer"}
// We have the opportunity to refine the existing answer
// (only if needed) with some more context below.
// ------------
// ${"context"}
// ------------
// If the existing answer was already true, still answer true.
// If the information is present in the new context, answer true.
// Otherwise answer false.

// Example Response:
// {
// "faithful": true
// }
// `;

export const FAITHFULNESS_SYSTEM_PROMPT = prmpt`
Please tell if a given piece of information is supported by the context.

Expand Down Expand Up @@ -94,27 +75,6 @@ ${"context"}
------------
`;

// export const RELEVANCY_REFINE_SYSTEM_PROMPT = prmpt`
// We want to understand if the following query and response is
// in line with the context information:
// ${"query"}
// We have provided an existing answer:
// ${"existingAnswer"}
// We have the opportunity to refine the existing answer
// (only if needed) with some more context below.
// ------------
// ${"context"}
// ------------
// If the existing answer was already true, still answer true.
// If the information is present in the new context, answer true.
// Otherwise answer false.

// Example Response:
// {
// "relevant": true
// }
// `;

export const RELEVANCY_SYSTEM_PROMPT = prmpt`
Your task is to evaluate if the response for the query is in line with the context information provided.
Answer with a JSON object with a single boolean field "relevant".
Expand Down
47 changes: 21 additions & 26 deletions apps/web/src/app/api/(internal-api)/benchmark/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AgentsetApiError } from "@/lib/api/errors";
import { withAuthApiHandler } from "@/lib/api/handler";
import { makeApiSuccessResponse } from "@/lib/api/response";
import { parseRequestBody } from "@/lib/api/utils";
import { NEW_MESSAGE_PROMPT } from "@/lib/prompts";
import { DEFAULT_SYSTEM_PROMPT, NEW_MESSAGE_PROMPT } from "@/lib/prompts";
import { waitUntil } from "@vercel/functions";
import { generateText } from "ai";

Expand All @@ -16,6 +16,7 @@ import {
getNamespaceVectorStore,
queryVectorStore,
} from "@agentset/engine";
import { DEFAULT_RERANKER } from "@agentset/validation";

import { chatSchema } from "./schema";
import { correctnessEval, faithfulnessEval, relevanceEval } from "./utils";
Expand Down Expand Up @@ -69,41 +70,35 @@ export const POST = withAuthApiHandler(
};

if (body.mode === "agentic") {
result = await generateAgenticResponse({
const response = await generateAgenticResponse({
model: languageModel,
systemPrompt: body.systemPrompt,
temperature: body.temperature,
queryOptions: {
embeddingModel,
vectorStore,
topK: body.topK,
minScore: body.minScore,
filter: body.filter,
includeMetadata: body.includeMetadata,
includeRelationships: body.includeRelationships,
rerank: body.rerank
? { model: "cohere:rerank-v3.5", limit: body.rerankLimit }
: false,
},
messagesWithoutQuery: [],
lastMessage: message,
systemPrompt: DEFAULT_SYSTEM_PROMPT.compile(),
temperature: 0,
embeddingModel,
vectorStore,
messages: [
{
role: "user",
content: message,
},
],
afterQueries: (totalQueries) => {
incrementUsage(namespace.id, totalQueries);
},
});

result = {
answer: response.text,
sources: response.searchResults as QueryVectorStoreResult["results"],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the benchmark route file
find . -name "route.ts" -path "*/api/(internal-api)/benchmark*" | head -5

# Find the agentic index file
find . -name "index.ts" -path "*agentic*" | head -5

# Find QueryVectorStoreResult type
rg "type QueryVectorStoreResult" --type=ts -B2 -A5

Repository: agentset-ai/agentset

Length of output: 470


🏁 Script executed:

#!/bin/bash
# Read the benchmark route file around line 92
cat -n ./apps/web/src/app/api/(internal-api)/benchmark/route.ts | sed -n '85,100p'

# Read the agentic/index.ts to see generateAgenticResponse
head -100 ./apps/web/src/lib/agentic/index.ts

# Read queryVectorStore function to understand QueryVectorStoreResult
cat -n ./packages/engine/src/vector-store/query.ts | head -80

Repository: agentset-ai/agentset

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Find and read the benchmark route file around line 92
BENCHMARK_FILE=$(find . -path "*/api/(internal-api)/benchmark/route.ts" -type f)
if [ -n "$BENCHMARK_FILE" ]; then
  cat -n "$BENCHMARK_FILE" | sed -n '85,100p'
fi

# Read the agentic/index.ts to see generateAgenticResponse
head -150 ./apps/web/src/lib/agentic/index.ts

# Read queryVectorStore function to understand QueryVectorStoreResult
cat -n ./packages/engine/src/vector-store/query.ts | head -100

Repository: agentset-ai/agentset

Length of output: 4819


Add explicit return type to generateAgenticResponse or validate searchResults shape.

The cast as QueryVectorStoreResult["results"] assumes response.searchResults matches the expected type, but searchResults in generateAgenticResponse is constructed from tool output without explicit type annotations. Consider explicitly typing the return type of generateAgenticResponse to include searchResults: QueryVectorStoreResult["results"], or add runtime validation before the cast.

🤖 Prompt for AI Agents
In apps/web/src/app/api/(internal-api)/benchmark/route.ts around line 92, the
code unsafely casts response.searchResults to QueryVectorStoreResult["results"];
update generateAgenticResponse to explicitly declare its return type including
searchResults: QueryVectorStoreResult["results"] so the compiler guarantees
shape, or add a small runtime validation before the cast (e.g., check
Array.isArray and required item properties) and throw/log a descriptive error if
validation fails; ensure the function signature and any upstream callers are
updated to use the new typed return value.

};
} else {
const data = await queryVectorStore({
embeddingModel,
vectorStore,
query: message,
topK: body.topK,
minScore: body.minScore,
filter: body.filter,
includeMetadata: body.includeMetadata,
includeRelationships: body.includeRelationships,
rerank: body.rerank
? { model: "cohere:rerank-v3.5", limit: body.rerankLimit }
: false,
topK: 50,
includeMetadata: true,
rerank: { model: DEFAULT_RERANKER, limit: 15 },
});

const newMessages: ModelMessage[] = [
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/(internal-api)/benchmark/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { LanguageModel } from "ai";
import { formatSources } from "@/lib/agentic/utils";
import { formatSources } from "@/lib/prompts";
import { generateText } from "ai";
import { z } from "zod/v4";

Expand Down
34 changes: 13 additions & 21 deletions apps/web/src/app/api/(internal-api)/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ModelMessage } from "ai";
import agenticPipeline from "@/lib/agentic";
import { streamAgenticResponse } from "@/lib/agentic";
import { AgentsetApiError } from "@/lib/api/errors";
import { withAuthApiHandler } from "@/lib/api/handler";
import { parseRequestBody } from "@/lib/api/utils";
Expand Down Expand Up @@ -145,37 +145,29 @@ export const POST = withAuthApiHandler(
? new KeywordStore(namespace.id, tenantId)
: undefined;

const result = agenticPipeline({
const result = streamAgenticResponse({
model: languageModel,
keywordStore,
queryOptions: {
embeddingModel,
vectorStore,
topK: body.topK,
minScore: body.minScore,
filter: body.filter,
includeMetadata: body.includeMetadata,
includeRelationships: body.includeRelationships,
rerank: body.rerank
? {
model: body.rerankModel,
limit: body.rerankLimit,
}
: false,
},
embeddingModel,
vectorStore,
topK: body.topK,
rerank: body.rerank
? {
model: body.rerankModel,
limit: body.rerankLimit,
}
: undefined,
systemPrompt: body.systemPrompt,
temperature: body.temperature,
messagesWithoutQuery,
lastMessage,
messages,
afterQueries: (totalQueries) => {
incrementUsage(namespace.id, totalQueries);
},
});

return result;
return result.toUIMessageStreamResponse({ headers });
}

// TODO: track the usage
const data = await queryVectorStore({
embeddingModel,
vectorStore,
Expand Down
33 changes: 11 additions & 22 deletions apps/web/src/app/api/(internal-api)/hosting-chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import agenticPipeline from "@/lib/agentic";
import { streamAgenticResponse } from "@/lib/agentic";
import { AgentsetApiError } from "@/lib/api/errors";
import { withPublicApiHandler } from "@/lib/api/handler/public";
import { hostingAuth } from "@/lib/api/hosting-auth";
import { parseRequestBody } from "@/lib/api/utils";
import { DEFAULT_SYSTEM_PROMPT } from "@/lib/prompts";
import { extractTextFromParts } from "@/lib/string-utils";
import { waitUntil } from "@vercel/functions";
import { convertToModelMessages } from "ai";

Expand Down Expand Up @@ -75,13 +74,8 @@ export const POST = withPublicApiHandler(
);

const messages = convertToModelMessages(body.messages);
const messagesWithoutQuery = messages.slice(0, -1);
const lastMessage =
messages.length > 0
? extractTextFromParts(messages[messages.length - 1]!.content)
: null;

if (!lastMessage) {
if (messages.length === 0) {
throw new AgentsetApiError({
code: "bad_request",
message: "Messages must contain at least one message",
Expand Down Expand Up @@ -116,29 +110,24 @@ export const POST = withPublicApiHandler(
? new KeywordStore(hosting.namespace.id)
: undefined;

const result = agenticPipeline({
const result = streamAgenticResponse({
model: languageModel,
keywordStore,
queryOptions: {
embeddingModel,
vectorStore,
topK: hosting.topK,
rerank: {
model: hosting.rerankConfig?.model,
limit: hosting.rerankConfig?.limit ?? 15,
},
includeMetadata: true,
embeddingModel,
vectorStore,
topK: hosting.topK,
rerank: {
model: hosting.rerankConfig?.model,
limit: hosting.rerankConfig?.limit,
},
Comment on lines +119 to 122
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Possible issue: rerank object with undefined properties may bypass default reranking.

When hosting.rerankConfig is null, this creates rerank: { model: undefined, limit: undefined }. In tools.ts, the semanticSearchTool uses rerank ?? { model: "zeroentropy:zerank-2", limit: 10 }, so an object with undefined values won't trigger the fallback defaults.

Consider passing undefined when reranking is not configured:

🔎 Proposed fix
-      rerank: {
-        model: hosting.rerankConfig?.model,
-        limit: hosting.rerankConfig?.limit,
-      },
+      rerank: hosting.rerankConfig
+        ? {
+            model: hosting.rerankConfig.model,
+            limit: hosting.rerankConfig.limit,
+          }
+        : undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rerank: {
model: hosting.rerankConfig?.model,
limit: hosting.rerankConfig?.limit,
},
rerank: hosting.rerankConfig
? {
model: hosting.rerankConfig.model,
limit: hosting.rerankConfig.limit,
}
: undefined,
🤖 Prompt for AI Agents
In apps/web/src/app/api/(internal-api)/hosting-chat/route.ts around lines 119 to
122, constructing rerank as { model: hosting.rerankConfig?.model, limit:
hosting.rerankConfig?.limit } can produce an object with undefined fields that
prevents tools.ts from using its fallback; change the assignment to pass
undefined when rerankConfig is absent (e.g. set rerank to hosting.rerankConfig ?
{ model: hosting.rerankConfig.model, limit: hosting.rerankConfig.limit } :
undefined) so the semanticSearchTool can correctly apply its default rerank
settings.

systemPrompt: hosting.systemPrompt ?? DEFAULT_SYSTEM_PROMPT.compile(),
temperature: 0,
messagesWithoutQuery,
lastMessage,
messages,
afterQueries: (totalQueries) => {
incrementUsage(hosting.namespace.id, totalQueries);
},
headers,
});

return result;
return result.toUIMessageStreamResponse({ headers });
},
);
46 changes: 14 additions & 32 deletions apps/web/src/app/api/(internal-api)/hosting-search/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { agenticSearch } from "@/lib/agentic/search";
import { AgentsetApiError } from "@/lib/api/errors";
import { withPublicApiHandler } from "@/lib/api/handler/public";
import { hostingAuth } from "@/lib/api/hosting-auth";
Expand All @@ -9,9 +8,8 @@ import { parseRequestBody } from "@/lib/api/utils";
import { db } from "@agentset/db/client";
import {
getNamespaceEmbeddingModel,
getNamespaceLanguageModel,
getNamespaceVectorStore,
KeywordStore,
queryVectorStore,
} from "@agentset/engine";

import { hostingSearchSchema } from "./schema";
Expand Down Expand Up @@ -74,44 +72,28 @@ export const POST = withPublicApiHandler(
});
}

const [languageModel, vectorStore, embeddingModel] = await Promise.all([
getNamespaceLanguageModel(hosting.llmConfig?.model),
const [vectorStore, embeddingModel] = await Promise.all([
getNamespaceVectorStore(hosting.namespace),
getNamespaceEmbeddingModel(hosting.namespace, "query"),
]);

const keywordStore = hosting.namespace.keywordEnabled
? new KeywordStore(hosting.namespace.id)
: undefined;

const result = await agenticSearch({
model: languageModel,
queryOptions: {
embeddingModel,
vectorStore,
topK: hosting.topK,
rerank: {
model: hosting.rerankConfig?.model,
limit: hosting.rerankConfig?.limit ?? 15,
},
includeMetadata: true,
const result = await queryVectorStore({
embeddingModel,
vectorStore,
query: body.query,
mode: "semantic",
topK: hosting.topK,
rerank: {
model: hosting.rerankConfig?.model,
limit: hosting.rerankConfig?.limit ?? 15,
},
messages: [
{
role: "user",
content: body.query,
},
],
includeMetadata: true,
});

incrementSearchUsage(hosting.namespace.id, result.totalQueries);
incrementSearchUsage(hosting.namespace.id, 1);

return makeApiSuccessResponse({
data: {
totalQueries: result.totalQueries,
queries: result.queries,
chunks: Object.values(result.chunks),
},
data: result.results,
headers,
});
},
Expand Down
Loading