Augment system prompt with date and user location (default SF)
This commit is contained in:
@@ -661,6 +661,7 @@ struct CompletionStreamRequest: Codable, Sendable {
|
|||||||
var provider: Provider
|
var provider: Provider
|
||||||
var model: String
|
var model: String
|
||||||
var messages: [CompletionRequestMessage]
|
var messages: [CompletionRequestMessage]
|
||||||
|
var userLocation: String? = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ChatCreateBody: Encodable {
|
private struct ChatCreateBody: Encodable {
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import { z } from "zod";
|
|||||||
import { env } from "../env.js";
|
import { env } from "../env.js";
|
||||||
import { exaClient } from "../search/exa.js";
|
import { exaClient } from "../search/exa.js";
|
||||||
import { searchSearxng } from "../search/searxng.js";
|
import { searchSearxng } from "../search/searxng.js";
|
||||||
import { buildOpenAIConversationMessage, buildOpenAIResponsesInputMessage } from "./message-content.js";
|
import {
|
||||||
|
buildOpenAIConversationMessage,
|
||||||
|
buildOpenAIResponsesInputMessage,
|
||||||
|
buildSystemPromptAugmentationMessage,
|
||||||
|
} from "./message-content.js";
|
||||||
import type { ChatMessage } from "./types.js";
|
import type { ChatMessage } from "./types.js";
|
||||||
|
|
||||||
const MAX_TOOL_ROUNDS = env.CHAT_MAX_TOOL_ROUNDS;
|
const MAX_TOOL_ROUNDS = env.CHAT_MAX_TOOL_ROUNDS;
|
||||||
@@ -239,6 +243,7 @@ type ToolAwareCompletionParams = {
|
|||||||
client: OpenAI;
|
client: OpenAI;
|
||||||
model: string;
|
model: string;
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
|
userLocation?: string;
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
maxTokens?: number;
|
maxTokens?: number;
|
||||||
onToolEvent?: (event: ToolExecutionEvent) => void | Promise<void>;
|
onToolEvent?: (event: ToolExecutionEvent) => void | Promise<void>;
|
||||||
@@ -379,20 +384,20 @@ function extractHtmlTitle(html: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeIncomingMessages(messages: ChatMessage[]) {
|
function normalizeIncomingMessages(messages: ChatMessage[], userLocation?: string) {
|
||||||
const normalized = messages.map((message) => buildOpenAIConversationMessage(message));
|
const normalized = messages.map((message) => buildOpenAIConversationMessage(message));
|
||||||
|
|
||||||
return [{ role: "system", content: CHAT_TOOL_SYSTEM_PROMPT }, ...normalized];
|
return [{ role: "system", content: CHAT_TOOL_SYSTEM_PROMPT }, buildSystemPromptAugmentationMessage(userLocation), ...normalized];
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePlainIncomingMessages(messages: ChatMessage[]) {
|
function normalizePlainIncomingMessages(messages: ChatMessage[], userLocation?: string) {
|
||||||
return messages.map((message) => buildOpenAIConversationMessage(message));
|
return [buildSystemPromptAugmentationMessage(userLocation), ...messages.map((message) => buildOpenAIConversationMessage(message))];
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeIncomingResponsesInput(messages: ChatMessage[]) {
|
function normalizeIncomingResponsesInput(messages: ChatMessage[], userLocation?: string) {
|
||||||
const normalized = messages.map((message) => buildOpenAIResponsesInputMessage(message));
|
const normalized = messages.map((message) => buildOpenAIResponsesInputMessage(message));
|
||||||
|
|
||||||
return [{ role: "system", content: CHAT_TOOL_SYSTEM_PROMPT }, ...normalized];
|
return [{ role: "system", content: CHAT_TOOL_SYSTEM_PROMPT }, buildSystemPromptAugmentationMessage(userLocation), ...normalized];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runExaWebSearchTool(args: WebSearchArgs): Promise<ToolRunOutcome> {
|
async function runExaWebSearchTool(args: WebSearchArgs): Promise<ToolRunOutcome> {
|
||||||
@@ -957,7 +962,7 @@ async function executeToolCallAndBuildEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runToolAwareOpenAIChat(params: ToolAwareCompletionParams): Promise<ToolAwareCompletionResult> {
|
export async function runToolAwareOpenAIChat(params: ToolAwareCompletionParams): Promise<ToolAwareCompletionResult> {
|
||||||
const input: any[] = normalizeIncomingResponsesInput(params.messages);
|
const input: any[] = normalizeIncomingResponsesInput(params.messages, params.userLocation);
|
||||||
const rawResponses: unknown[] = [];
|
const rawResponses: unknown[] = [];
|
||||||
const toolEvents: ToolExecutionEvent[] = [];
|
const toolEvents: ToolExecutionEvent[] = [];
|
||||||
const usageAcc: Required<ToolAwareUsage> = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
const usageAcc: Required<ToolAwareUsage> = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
||||||
@@ -1026,7 +1031,7 @@ export async function runToolAwareOpenAIChat(params: ToolAwareCompletionParams):
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runToolAwareChatCompletions(params: ToolAwareCompletionParams): Promise<ToolAwareCompletionResult> {
|
export async function runToolAwareChatCompletions(params: ToolAwareCompletionParams): Promise<ToolAwareCompletionResult> {
|
||||||
const conversation: any[] = normalizeIncomingMessages(params.messages);
|
const conversation: any[] = normalizeIncomingMessages(params.messages, params.userLocation);
|
||||||
const rawResponses: unknown[] = [];
|
const rawResponses: unknown[] = [];
|
||||||
const toolEvents: ToolExecutionEvent[] = [];
|
const toolEvents: ToolExecutionEvent[] = [];
|
||||||
const usageAcc: Required<ToolAwareUsage> = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
const usageAcc: Required<ToolAwareUsage> = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
||||||
@@ -1114,7 +1119,7 @@ export async function runToolAwareChatCompletions(params: ToolAwareCompletionPar
|
|||||||
export async function runPlainChatCompletions(params: ToolAwareCompletionParams): Promise<ToolAwareCompletionResult> {
|
export async function runPlainChatCompletions(params: ToolAwareCompletionParams): Promise<ToolAwareCompletionResult> {
|
||||||
const completion = await params.client.chat.completions.create({
|
const completion = await params.client.chat.completions.create({
|
||||||
model: params.model,
|
model: params.model,
|
||||||
messages: normalizePlainIncomingMessages(params.messages),
|
messages: normalizePlainIncomingMessages(params.messages, params.userLocation),
|
||||||
temperature: params.temperature,
|
temperature: params.temperature,
|
||||||
max_tokens: params.maxTokens,
|
max_tokens: params.maxTokens,
|
||||||
} as any);
|
} as any);
|
||||||
@@ -1134,7 +1139,7 @@ export async function runPlainChatCompletions(params: ToolAwareCompletionParams)
|
|||||||
export async function* runToolAwareOpenAIChatStream(
|
export async function* runToolAwareOpenAIChatStream(
|
||||||
params: ToolAwareCompletionParams
|
params: ToolAwareCompletionParams
|
||||||
): AsyncGenerator<ToolAwareStreamingEvent> {
|
): AsyncGenerator<ToolAwareStreamingEvent> {
|
||||||
const input: any[] = normalizeIncomingResponsesInput(params.messages);
|
const input: any[] = normalizeIncomingResponsesInput(params.messages, params.userLocation);
|
||||||
const rawResponses: unknown[] = [];
|
const rawResponses: unknown[] = [];
|
||||||
const toolEvents: ToolExecutionEvent[] = [];
|
const toolEvents: ToolExecutionEvent[] = [];
|
||||||
const usageAcc: Required<ToolAwareUsage> = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
const usageAcc: Required<ToolAwareUsage> = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
||||||
@@ -1260,7 +1265,7 @@ export async function* runToolAwareOpenAIChatStream(
|
|||||||
export async function* runToolAwareChatCompletionsStream(
|
export async function* runToolAwareChatCompletionsStream(
|
||||||
params: ToolAwareCompletionParams
|
params: ToolAwareCompletionParams
|
||||||
): AsyncGenerator<ToolAwareStreamingEvent> {
|
): AsyncGenerator<ToolAwareStreamingEvent> {
|
||||||
const conversation: any[] = normalizeIncomingMessages(params.messages);
|
const conversation: any[] = normalizeIncomingMessages(params.messages, params.userLocation);
|
||||||
const rawResponses: unknown[] = [];
|
const rawResponses: unknown[] = [];
|
||||||
const toolEvents: ToolExecutionEvent[] = [];
|
const toolEvents: ToolExecutionEvent[] = [];
|
||||||
const usageAcc: Required<ToolAwareUsage> = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
const usageAcc: Required<ToolAwareUsage> = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
||||||
@@ -1403,7 +1408,7 @@ export async function* runPlainChatCompletionsStream(
|
|||||||
|
|
||||||
const stream = await params.client.chat.completions.create({
|
const stream = await params.client.chat.completions.create({
|
||||||
model: params.model,
|
model: params.model,
|
||||||
messages: normalizePlainIncomingMessages(params.messages),
|
messages: normalizePlainIncomingMessages(params.messages, params.userLocation),
|
||||||
temperature: params.temperature,
|
temperature: params.temperature,
|
||||||
max_tokens: params.maxTokens,
|
max_tokens: params.maxTokens,
|
||||||
stream: true,
|
stream: true,
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
import type { ChatAttachment, ChatImageAttachment, ChatMessage, ChatTextAttachment } from "./types.js";
|
import type { ChatAttachment, ChatImageAttachment, ChatMessage, ChatTextAttachment } from "./types.js";
|
||||||
|
|
||||||
|
const DEFAULT_USER_LOCATION = "San Francisco, CA";
|
||||||
|
|
||||||
|
function currentDateString(now = new Date()) {
|
||||||
|
return now.toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveUserLocation(userLocation?: string) {
|
||||||
|
return userLocation?.trim() || process.env.SYBIL_USER_LOCATION?.trim() || DEFAULT_USER_LOCATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSystemPromptAugmentation(userLocation?: string, now = new Date()) {
|
||||||
|
return `Current date: ${currentDateString(now)}.\nUser location: ${resolveUserLocation(userLocation)}.`;
|
||||||
|
}
|
||||||
|
|
||||||
function escapeAttribute(value: string) {
|
function escapeAttribute(value: string) {
|
||||||
return value.replace(/"/g, """);
|
return value.replace(/"/g, """);
|
||||||
}
|
}
|
||||||
@@ -198,11 +212,18 @@ export function buildOpenAIResponsesInputMessage(message: ChatMessage) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildSystemPromptAugmentationMessage(userLocation?: string) {
|
||||||
|
return {
|
||||||
|
role: "system",
|
||||||
|
content: buildSystemPromptAugmentation(userLocation),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const ANTHROPIC_NO_SERVER_TOOLS_PROMPT =
|
const ANTHROPIC_NO_SERVER_TOOLS_PROMPT =
|
||||||
"This Anthropic backend path does not have server-managed tool calls. Do not claim to run shell commands, Codex tasks, web searches, or fetch URLs. If the user asks for tool execution, explain that they should switch to OpenAI or xAI in this app for tool-enabled chat.";
|
"This Anthropic backend path does not have server-managed tool calls. Do not claim to run shell commands, Codex tasks, web searches, or fetch URLs. If the user asks for tool execution, explain that they should switch to OpenAI or xAI in this app for tool-enabled chat.";
|
||||||
|
|
||||||
export function getAnthropicSystemPrompt(messages: ChatMessage[]) {
|
export function getAnthropicSystemPrompt(messages: ChatMessage[], userLocation?: string) {
|
||||||
return [ANTHROPIC_NO_SERVER_TOOLS_PROMPT, messages.find((message) => message.role === "system")?.content]
|
return [ANTHROPIC_NO_SERVER_TOOLS_PROMPT, buildSystemPromptAugmentation(userLocation), messages.find((message) => message.role === "system")?.content]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export async function runMultiplex(req: MultiplexRequest): Promise<MultiplexResp
|
|||||||
client,
|
client,
|
||||||
model: req.model,
|
model: req.model,
|
||||||
messages: req.messages,
|
messages: req.messages,
|
||||||
|
userLocation: req.userLocation,
|
||||||
temperature: req.temperature,
|
temperature: req.temperature,
|
||||||
maxTokens: req.maxTokens,
|
maxTokens: req.maxTokens,
|
||||||
logContext: {
|
logContext: {
|
||||||
@@ -72,6 +73,7 @@ export async function runMultiplex(req: MultiplexRequest): Promise<MultiplexResp
|
|||||||
client,
|
client,
|
||||||
model: req.model,
|
model: req.model,
|
||||||
messages: req.messages,
|
messages: req.messages,
|
||||||
|
userLocation: req.userLocation,
|
||||||
temperature: req.temperature,
|
temperature: req.temperature,
|
||||||
maxTokens: req.maxTokens,
|
maxTokens: req.maxTokens,
|
||||||
logContext: {
|
logContext: {
|
||||||
@@ -90,6 +92,7 @@ export async function runMultiplex(req: MultiplexRequest): Promise<MultiplexResp
|
|||||||
client,
|
client,
|
||||||
model: req.model,
|
model: req.model,
|
||||||
messages: req.messages,
|
messages: req.messages,
|
||||||
|
userLocation: req.userLocation,
|
||||||
temperature: req.temperature,
|
temperature: req.temperature,
|
||||||
maxTokens: req.maxTokens,
|
maxTokens: req.maxTokens,
|
||||||
logContext: {
|
logContext: {
|
||||||
@@ -104,7 +107,7 @@ export async function runMultiplex(req: MultiplexRequest): Promise<MultiplexResp
|
|||||||
} else if (req.provider === "anthropic") {
|
} else if (req.provider === "anthropic") {
|
||||||
const client = anthropicClient();
|
const client = anthropicClient();
|
||||||
|
|
||||||
const system = getAnthropicSystemPrompt(req.messages);
|
const system = getAnthropicSystemPrompt(req.messages, req.userLocation);
|
||||||
const msgs = req.messages.filter((message) => message.role !== "system").map((message) => buildAnthropicConversationMessage(message));
|
const msgs = req.messages.filter((message) => message.role !== "system").map((message) => buildAnthropicConversationMessage(message));
|
||||||
|
|
||||||
const r = await client.messages.create({
|
const r = await client.messages.create({
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export async function* runMultiplexStream(req: MultiplexRequest): AsyncGenerator
|
|||||||
client,
|
client,
|
||||||
model: req.model,
|
model: req.model,
|
||||||
messages: req.messages,
|
messages: req.messages,
|
||||||
|
userLocation: req.userLocation,
|
||||||
temperature: req.temperature,
|
temperature: req.temperature,
|
||||||
maxTokens: req.maxTokens,
|
maxTokens: req.maxTokens,
|
||||||
logContext: {
|
logContext: {
|
||||||
@@ -95,6 +96,7 @@ export async function* runMultiplexStream(req: MultiplexRequest): AsyncGenerator
|
|||||||
client,
|
client,
|
||||||
model: req.model,
|
model: req.model,
|
||||||
messages: req.messages,
|
messages: req.messages,
|
||||||
|
userLocation: req.userLocation,
|
||||||
temperature: req.temperature,
|
temperature: req.temperature,
|
||||||
maxTokens: req.maxTokens,
|
maxTokens: req.maxTokens,
|
||||||
logContext: {
|
logContext: {
|
||||||
@@ -107,6 +109,7 @@ export async function* runMultiplexStream(req: MultiplexRequest): AsyncGenerator
|
|||||||
client,
|
client,
|
||||||
model: req.model,
|
model: req.model,
|
||||||
messages: req.messages,
|
messages: req.messages,
|
||||||
|
userLocation: req.userLocation,
|
||||||
temperature: req.temperature,
|
temperature: req.temperature,
|
||||||
maxTokens: req.maxTokens,
|
maxTokens: req.maxTokens,
|
||||||
logContext: {
|
logContext: {
|
||||||
@@ -146,7 +149,7 @@ export async function* runMultiplexStream(req: MultiplexRequest): AsyncGenerator
|
|||||||
} else if (req.provider === "anthropic") {
|
} else if (req.provider === "anthropic") {
|
||||||
const client = anthropicClient();
|
const client = anthropicClient();
|
||||||
|
|
||||||
const system = getAnthropicSystemPrompt(req.messages);
|
const system = getAnthropicSystemPrompt(req.messages, req.userLocation);
|
||||||
const msgs = req.messages.filter((message) => message.role !== "system").map((message) => buildAnthropicConversationMessage(message));
|
const msgs = req.messages.filter((message) => message.role !== "system").map((message) => buildAnthropicConversationMessage(message));
|
||||||
|
|
||||||
const stream = await client.messages.create({
|
const stream = await client.messages.create({
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type MultiplexRequest = {
|
|||||||
provider: Provider;
|
provider: Provider;
|
||||||
model: string;
|
model: string;
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
|
userLocation?: string;
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
maxTokens?: number;
|
maxTokens?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,43 @@ function isToolCallLogMessage(message: { role: string; metadata: unknown }) {
|
|||||||
return message.role === "tool" && isToolCallLogMetadata(message.metadata);
|
return message.role === "tool" && isToolCallLogMetadata(message.metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHeaderString(req: FastifyRequest, name: string) {
|
||||||
|
const value = req.headers[name.toLowerCase()];
|
||||||
|
if (Array.isArray(value)) return value.find((item) => item.trim());
|
||||||
|
return typeof value === "string" && value.trim() ? value : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeHeaderPart(value: string | undefined) {
|
||||||
|
if (!value) return undefined;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return undefined;
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(trimmed);
|
||||||
|
} catch {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferRequestUserLocation(req: FastifyRequest) {
|
||||||
|
const explicit = decodeHeaderPart(getHeaderString(req, "x-user-location"));
|
||||||
|
if (explicit) return explicit;
|
||||||
|
|
||||||
|
const vercelCity = decodeHeaderPart(getHeaderString(req, "x-vercel-ip-city"));
|
||||||
|
const vercelRegion = decodeHeaderPart(getHeaderString(req, "x-vercel-ip-country-region"));
|
||||||
|
const vercelCountry = decodeHeaderPart(getHeaderString(req, "x-vercel-ip-country"));
|
||||||
|
const vercelLocation = [vercelCity, vercelRegion, vercelCountry].filter(Boolean).join(", ");
|
||||||
|
if (vercelLocation) return vercelLocation;
|
||||||
|
|
||||||
|
const cfCity = decodeHeaderPart(getHeaderString(req, "cf-ipcity"));
|
||||||
|
const cfRegion = decodeHeaderPart(getHeaderString(req, "cf-region"));
|
||||||
|
const cfCountry = decodeHeaderPart(getHeaderString(req, "cf-ipcountry"));
|
||||||
|
return [cfCity, cfRegion, cfCountry].filter(Boolean).join(", ") || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withRequestUserLocation<T extends { userLocation?: string }>(body: T, req: FastifyRequest): T {
|
||||||
|
return body.userLocation ? body : { ...body, userLocation: inferRequestUserLocation(req) };
|
||||||
|
}
|
||||||
|
|
||||||
async function storeNonAssistantMessages(chatId: string, messages: IncomingChatMessage[]) {
|
async function storeNonAssistantMessages(chatId: string, messages: IncomingChatMessage[]) {
|
||||||
const incoming = messages.filter((m) => m.role !== "assistant");
|
const incoming = messages.filter((m) => m.role !== "assistant");
|
||||||
if (!incoming.length) return;
|
if (!incoming.length) return;
|
||||||
@@ -132,6 +169,7 @@ const CompletionStreamBody = z
|
|||||||
provider: ProviderSchema,
|
provider: ProviderSchema,
|
||||||
model: z.string().min(1),
|
model: z.string().min(1),
|
||||||
messages: z.array(CompletionMessageSchema),
|
messages: z.array(CompletionMessageSchema),
|
||||||
|
userLocation: z.string().trim().min(1).max(200).optional(),
|
||||||
temperature: z.number().min(0).max(2).optional(),
|
temperature: z.number().min(0).max(2).optional(),
|
||||||
maxTokens: z.number().int().positive().optional(),
|
maxTokens: z.number().int().positive().optional(),
|
||||||
})
|
})
|
||||||
@@ -1211,13 +1249,14 @@ export async function registerRoutes(app: FastifyInstance) {
|
|||||||
provider: ProviderSchema,
|
provider: ProviderSchema,
|
||||||
model: z.string().min(1),
|
model: z.string().min(1),
|
||||||
messages: z.array(CompletionMessageSchema),
|
messages: z.array(CompletionMessageSchema),
|
||||||
|
userLocation: z.string().trim().min(1).max(200).optional(),
|
||||||
temperature: z.number().min(0).max(2).optional(),
|
temperature: z.number().min(0).max(2).optional(),
|
||||||
maxTokens: z.number().int().positive().optional(),
|
maxTokens: z.number().int().positive().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsed = Body.safeParse(req.body);
|
const parsed = Body.safeParse(req.body);
|
||||||
if (!parsed.success) return app.httpErrors.badRequest(parsed.error.message);
|
if (!parsed.success) return app.httpErrors.badRequest(parsed.error.message);
|
||||||
const body = parsed.data;
|
const body = withRequestUserLocation(parsed.data, req);
|
||||||
|
|
||||||
// ensure chat exists if provided
|
// ensure chat exists if provided
|
||||||
if (body.chatId) {
|
if (body.chatId) {
|
||||||
@@ -1244,7 +1283,7 @@ export async function registerRoutes(app: FastifyInstance) {
|
|||||||
|
|
||||||
const parsed = CompletionStreamBody.safeParse(req.body);
|
const parsed = CompletionStreamBody.safeParse(req.body);
|
||||||
if (!parsed.success) return app.httpErrors.badRequest(parsed.error.message);
|
if (!parsed.success) return app.httpErrors.badRequest(parsed.error.message);
|
||||||
const body = parsed.data;
|
const body = withRequestUserLocation(parsed.data, req);
|
||||||
|
|
||||||
// ensure chat exists if provided
|
// ensure chat exists if provided
|
||||||
if (body.chatId) {
|
if (body.chatId) {
|
||||||
|
|||||||
26
server/tests/message-content.test.ts
Normal file
26
server/tests/message-content.test.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import { buildSystemPromptAugmentation, getAnthropicSystemPrompt } from "../src/llm/message-content.js";
|
||||||
|
|
||||||
|
test("system prompt augmentation includes date and default location", () => {
|
||||||
|
const prompt = buildSystemPromptAugmentation(undefined, new Date("2026-05-24T15:30:00Z"));
|
||||||
|
|
||||||
|
assert.equal(prompt, "Current date: 2026-05-24.\nUser location: San Francisco, CA.");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("system prompt augmentation uses provided user location", () => {
|
||||||
|
const prompt = buildSystemPromptAugmentation("New York, NY", new Date("2026-05-24T15:30:00Z"));
|
||||||
|
|
||||||
|
assert.equal(prompt, "Current date: 2026-05-24.\nUser location: New York, NY.");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Anthropic system prompt includes runtime context with existing system messages", () => {
|
||||||
|
const prompt = getAnthropicSystemPrompt(
|
||||||
|
[{ role: "system", content: "Use concise answers." }],
|
||||||
|
"Los Angeles, CA"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.match(prompt, /Current date: \d{4}-\d{2}-\d{2}\./);
|
||||||
|
assert.match(prompt, /User location: Los Angeles, CA\./);
|
||||||
|
assert.match(prompt, /Use concise answers\./);
|
||||||
|
});
|
||||||
@@ -124,6 +124,7 @@ export class SybilApiClient {
|
|||||||
provider: Provider;
|
provider: Provider;
|
||||||
model: string;
|
model: string;
|
||||||
messages: CompletionRequestMessage[];
|
messages: CompletionRequestMessage[];
|
||||||
|
userLocation?: string;
|
||||||
},
|
},
|
||||||
handlers: CompletionStreamHandlers,
|
handlers: CompletionStreamHandlers,
|
||||||
options?: { signal?: AbortSignal }
|
options?: { signal?: AbortSignal }
|
||||||
|
|||||||
@@ -613,6 +613,7 @@ export async function runCompletion(body: {
|
|||||||
provider: Provider;
|
provider: Provider;
|
||||||
model: string;
|
model: string;
|
||||||
messages: CompletionRequestMessage[];
|
messages: CompletionRequestMessage[];
|
||||||
|
userLocation?: string;
|
||||||
}) {
|
}) {
|
||||||
return api<CompletionResponse>("/v1/chat-completions", {
|
return api<CompletionResponse>("/v1/chat-completions", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -627,6 +628,7 @@ export async function runCompletionStream(
|
|||||||
provider: Provider;
|
provider: Provider;
|
||||||
model: string;
|
model: string;
|
||||||
messages: CompletionRequestMessage[];
|
messages: CompletionRequestMessage[];
|
||||||
|
userLocation?: string;
|
||||||
},
|
},
|
||||||
handlers: CompletionStreamHandlers,
|
handlers: CompletionStreamHandlers,
|
||||||
options?: { signal?: AbortSignal }
|
options?: { signal?: AbortSignal }
|
||||||
|
|||||||
Reference in New Issue
Block a user