mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2026-01-12 08:55:48 +00:00
Compare commits
6 Commits
3e90305c12
...
046f159528
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
046f159528 | ||
|
|
6899b49ca0 | ||
|
|
dbc2137efb | ||
|
|
1ea348ddb7 | ||
|
|
b8a7fb936f | ||
|
|
33c8f454a3 |
@@ -305,7 +305,7 @@ const Weather = ({
|
||||
<div>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-4xl font-bold drop-shadow-md">
|
||||
{Math.round(current.temperature_2m)}°
|
||||
{current.temperature_2m}°
|
||||
</span>
|
||||
<span className="text-lg">F C</span>
|
||||
</div>
|
||||
@@ -316,8 +316,7 @@ const Weather = ({
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-xs font-medium opacity-90">
|
||||
{Math.round(daily.temperature_2m_max[0])}°{' '}
|
||||
{Math.round(daily.temperature_2m_min[0])}°
|
||||
{daily.temperature_2m_max[0]}° {daily.temperature_2m_min[0]}°
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,9 @@ const schema = z.object({
|
||||
showStockWidget: z
|
||||
.boolean()
|
||||
.describe('Indicates whether to show the stock widget.'),
|
||||
showCalculationWidget: z
|
||||
.boolean()
|
||||
.describe('Indicates whether to show the calculation widget.'),
|
||||
}),
|
||||
standaloneFollowUp: z
|
||||
.string()
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { ResearcherOutput, SearchAgentInput } from './types';
|
||||
import SessionManager from '@/lib/session';
|
||||
import Classifier from './classifier';
|
||||
import { WidgetRegistry } from './widgets';
|
||||
import { classify } from './classifier';
|
||||
import Researcher from './researcher';
|
||||
import { getWriterPrompt } from '@/lib/prompts/search/writer';
|
||||
import fs from 'fs';
|
||||
import { WidgetExecutor } from './widgets';
|
||||
|
||||
class SearchAgent {
|
||||
async searchAsync(session: SessionManager, input: SearchAgentInput) {
|
||||
const classifier = new Classifier();
|
||||
|
||||
const classification = await classifier.classify({
|
||||
const classification = await classify({
|
||||
chatHistory: input.chatHistory,
|
||||
enabledSources: input.config.sources,
|
||||
query: input.followUp,
|
||||
llm: input.config.llm,
|
||||
});
|
||||
|
||||
const widgetPromise = WidgetRegistry.executeAll(classification.widgets, {
|
||||
const widgetPromise = WidgetExecutor.executeAll({
|
||||
classification,
|
||||
chatHistory: input.chatHistory,
|
||||
followUp: input.followUp,
|
||||
llm: input.config.llm,
|
||||
embedding: input.config.embedding,
|
||||
session: session,
|
||||
}).then((widgetOutputs) => {
|
||||
widgetOutputs.forEach((o) => {
|
||||
session.emitBlock({
|
||||
@@ -37,7 +35,7 @@ class SearchAgent {
|
||||
|
||||
let searchPromise: Promise<ResearcherOutput> | null = null;
|
||||
|
||||
if (!classification.skipSearch) {
|
||||
if (!classification.classification.skipSearch) {
|
||||
const researcher = new Researcher();
|
||||
searchPromise = researcher.research(session, {
|
||||
chatHistory: input.chatHistory,
|
||||
|
||||
@@ -26,7 +26,7 @@ const webSearchAction: ResearchAction<typeof actionSchema> = {
|
||||
name: 'web_search',
|
||||
description: actionDescription,
|
||||
schema: actionSchema,
|
||||
enabled: (config) => config.classification.intents.includes('web_search'),
|
||||
enabled: (config) => true,
|
||||
execute: async (input, _) => {
|
||||
let results: Chunk[] = [];
|
||||
|
||||
|
||||
@@ -61,9 +61,7 @@ class Researcher {
|
||||
maxIteration,
|
||||
);
|
||||
|
||||
const actionStream = input.config.llm.streamObject<
|
||||
z.infer<typeof schema>
|
||||
>({
|
||||
const actionStream = input.config.llm.streamObject<typeof schema>({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
@@ -19,26 +19,17 @@ export type SearchAgentInput = {
|
||||
config: SearchAgentConfig;
|
||||
};
|
||||
|
||||
export interface Intent {
|
||||
name: string;
|
||||
description: string;
|
||||
requiresSearch: boolean;
|
||||
enabled: (config: { sources: SearchSources[] }) => boolean;
|
||||
}
|
||||
|
||||
export type Widget<TSchema extends z.ZodObject<any> = z.ZodObject<any>> = {
|
||||
name: string;
|
||||
description: string;
|
||||
schema: TSchema;
|
||||
execute: (
|
||||
params: z.infer<TSchema>,
|
||||
additionalConfig: AdditionalConfig,
|
||||
) => Promise<WidgetOutput>;
|
||||
export type WidgetInput = {
|
||||
chatHistory: ChatTurnMessage[];
|
||||
followUp: string;
|
||||
classification: ClassifierOutput;
|
||||
llm: BaseLLM<any>;
|
||||
};
|
||||
|
||||
export type WidgetConfig = {
|
||||
export type Widget = {
|
||||
type: string;
|
||||
params: Record<string, any>;
|
||||
shouldExecute: (classification: ClassifierOutput) => boolean;
|
||||
execute: (input: WidgetInput) => Promise<WidgetOutput | void>;
|
||||
};
|
||||
|
||||
export type WidgetOutput = {
|
||||
@@ -62,6 +53,7 @@ export type ClassifierOutput = {
|
||||
discussionSearch: boolean;
|
||||
showWeatherWidget: boolean;
|
||||
showStockWidget: boolean;
|
||||
showCalculationWidget: boolean;
|
||||
};
|
||||
standaloneFollowUp: string;
|
||||
};
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
import z from 'zod';
|
||||
import { Widget } from '../types';
|
||||
import { evaluate as mathEval } from 'mathjs';
|
||||
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
||||
import { exp, evaluate as mathEval } from 'mathjs';
|
||||
|
||||
const schema = z.object({
|
||||
type: z.literal('calculation'),
|
||||
expression: z
|
||||
.string()
|
||||
.describe(
|
||||
"A valid mathematical expression to be evaluated (e.g., '2 + 2', '3 * (4 + 5)').",
|
||||
),
|
||||
.describe('Mathematical expression to calculate or evaluate.'),
|
||||
notPresent: z
|
||||
.boolean()
|
||||
.describe('Whether there is any need for the calculation widget.'),
|
||||
});
|
||||
|
||||
const calculationWidget: Widget<typeof schema> = {
|
||||
name: 'calculation',
|
||||
description: `Performs mathematical calculations and evaluates mathematical expressions. Supports arithmetic operations, algebraic equations, functions, and complex mathematical computations.
|
||||
const system = `
|
||||
<role>
|
||||
Assistant is a calculation expression extractor. You will recieve a user follow up and a conversation history.
|
||||
Your task is to determine if there is a mathematical expression that needs to be calculated or evaluated. If there is, extract the expression and return it. If there is no need for any calculation, set notPresent to true.
|
||||
</role>
|
||||
|
||||
**What it provides:**
|
||||
- Evaluates mathematical expressions and returns computed results
|
||||
- Handles basic arithmetic (+, -, *, /)
|
||||
- Supports functions (sqrt, sin, cos, log, etc.)
|
||||
- Can process complex expressions with parentheses and order of operations
|
||||
<instructions>
|
||||
Make sure that the extracted expression is valid and can be used to calculate the result with Math JS library (https://mathjs.org/). If the expression is not valid, set notPresent to true.
|
||||
If you feel like you cannot extract a valid expression, set notPresent to true.
|
||||
</instructions>
|
||||
|
||||
**When to use:**
|
||||
- User asks to calculate, compute, or evaluate a mathematical expression
|
||||
- Questions like "what is X", "calculate Y", "how much is Z" where X/Y/Z are math expressions
|
||||
- Any request involving numbers and mathematical operations
|
||||
|
||||
**Example call:**
|
||||
<output_format>
|
||||
You must respond in the following JSON format without any extra text, explanations or filler sentences:
|
||||
{
|
||||
"type": "calculation",
|
||||
"expression": "25% of 480"
|
||||
"expression": string,
|
||||
"notPresent": boolean
|
||||
}
|
||||
</output_format>
|
||||
`;
|
||||
|
||||
{
|
||||
"type": "calculation",
|
||||
"expression": "sqrt(144) + 5 * 2"
|
||||
}
|
||||
|
||||
**Important:** The expression must be valid mathematical syntax that can be evaluated by mathjs. Format percentages as "0.25 * 480" or "25% of 480". Do not include currency symbols, units, or non-mathematical text in the expression.`,
|
||||
schema: schema,
|
||||
execute: async (params, _) => {
|
||||
try {
|
||||
const result = mathEval(params.expression);
|
||||
|
||||
return {
|
||||
type: 'calculation_result',
|
||||
llmContext: `The result of the expression "${params.expression}" is ${result}.`,
|
||||
data: {
|
||||
expression: params.expression,
|
||||
result: result,
|
||||
const calculationWidget: Widget = {
|
||||
type: 'calculationWidget',
|
||||
shouldExecute: (classification) =>
|
||||
classification.classification.showCalculationWidget,
|
||||
execute: async (input) => {
|
||||
const output = await input.llm.generateObject<typeof schema>({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: system,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'calculation_result',
|
||||
llmContext: 'Failed to evaluate mathematical expression.',
|
||||
data: {
|
||||
expression: params.expression,
|
||||
result: `Error evaluating expression: ${error}`,
|
||||
{
|
||||
role: 'user',
|
||||
content: `<conversation_history>\n${formatChatHistoryAsString(input.chatHistory)}\n</conversation_history>\n<user_follow_up>\n${input.followUp}\n</user_follow_up>`,
|
||||
},
|
||||
};
|
||||
}
|
||||
],
|
||||
schema,
|
||||
});
|
||||
|
||||
const result = mathEval(output.expression);
|
||||
|
||||
return {
|
||||
type: 'calculation_result',
|
||||
llmContext: `The result of the calculation for the expression "${output.expression}" is: ${result}`,
|
||||
data: {
|
||||
expression: output.expression,
|
||||
result,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
36
src/lib/agents/search/widgets/executor.ts
Normal file
36
src/lib/agents/search/widgets/executor.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Widget, WidgetInput, WidgetOutput } from '../types';
|
||||
|
||||
class WidgetExecutor {
|
||||
static widgets = new Map<string, Widget>();
|
||||
|
||||
static register(widget: Widget) {
|
||||
this.widgets.set(widget.type, widget);
|
||||
}
|
||||
|
||||
static getWidget(type: string): Widget | undefined {
|
||||
return this.widgets.get(type);
|
||||
}
|
||||
|
||||
static async executeAll(input: WidgetInput): Promise<WidgetOutput[]> {
|
||||
const results: WidgetOutput[] = [];
|
||||
|
||||
await Promise.all(
|
||||
Array.from(this.widgets.values()).map(async (widget) => {
|
||||
try {
|
||||
if (widget.shouldExecute(input.classification)) {
|
||||
const output = await widget.execute(input);
|
||||
if (output) {
|
||||
results.push(output);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Error executing widget ${widget.type}:`, e);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetExecutor;
|
||||
@@ -1,10 +1,10 @@
|
||||
import calculationWidget from './calculationWidget';
|
||||
import WidgetRegistry from './registry';
|
||||
import WidgetExecutor from './executor';
|
||||
import weatherWidget from './weatherWidget';
|
||||
import stockWidget from './stockWidget';
|
||||
|
||||
WidgetRegistry.register(weatherWidget);
|
||||
WidgetRegistry.register(calculationWidget);
|
||||
WidgetRegistry.register(stockWidget);
|
||||
WidgetExecutor.register(weatherWidget);
|
||||
WidgetExecutor.register(calculationWidget);
|
||||
WidgetExecutor.register(stockWidget);
|
||||
|
||||
export { WidgetRegistry };
|
||||
export { WidgetExecutor };
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import {
|
||||
AdditionalConfig,
|
||||
SearchAgentConfig,
|
||||
Widget,
|
||||
WidgetConfig,
|
||||
WidgetOutput,
|
||||
} from '../types';
|
||||
|
||||
class WidgetRegistry {
|
||||
private static widgets = new Map<string, Widget>();
|
||||
|
||||
static register(widget: Widget<any>) {
|
||||
this.widgets.set(widget.name, widget);
|
||||
}
|
||||
|
||||
static get(name: string): Widget | undefined {
|
||||
return this.widgets.get(name);
|
||||
}
|
||||
|
||||
static getAll(): Widget[] {
|
||||
return Array.from(this.widgets.values());
|
||||
}
|
||||
|
||||
static getDescriptions(): string {
|
||||
return Array.from(this.widgets.values())
|
||||
.map((widget) => `${widget.name}: ${widget.description}`)
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
static async execute(
|
||||
name: string,
|
||||
params: any,
|
||||
config: AdditionalConfig,
|
||||
): Promise<WidgetOutput> {
|
||||
const widget = this.get(name);
|
||||
|
||||
if (!widget) {
|
||||
throw new Error(`Widget with name ${name} not found`);
|
||||
}
|
||||
|
||||
return widget.execute(params, config);
|
||||
}
|
||||
|
||||
static async executeAll(
|
||||
widgets: WidgetConfig[],
|
||||
additionalConfig: AdditionalConfig,
|
||||
): Promise<WidgetOutput[]> {
|
||||
const results: WidgetOutput[] = [];
|
||||
|
||||
await Promise.all(
|
||||
widgets.map(async (widgetConfig) => {
|
||||
const output = await this.execute(
|
||||
widgetConfig.type,
|
||||
widgetConfig.params,
|
||||
additionalConfig,
|
||||
);
|
||||
results.push(output);
|
||||
}),
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetRegistry;
|
||||
@@ -1,13 +1,13 @@
|
||||
import z from 'zod';
|
||||
import { Widget } from '../types';
|
||||
import YahooFinance from 'yahoo-finance2';
|
||||
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
||||
|
||||
const yf = new YahooFinance({
|
||||
suppressNotices: ['yahooSurvey'],
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
type: z.literal('stock'),
|
||||
name: z
|
||||
.string()
|
||||
.describe(
|
||||
@@ -19,60 +19,59 @@ const schema = z.object({
|
||||
.describe(
|
||||
"Optional array of up to 3 stock names to compare against the base name (e.g., ['Microsoft', 'GOOGL', 'Meta']). Charts will show percentage change comparison.",
|
||||
),
|
||||
notPresent: z
|
||||
.boolean()
|
||||
.describe('Whether there is no need for the stock widget.'),
|
||||
});
|
||||
|
||||
const stockWidget: Widget<typeof schema> = {
|
||||
name: 'stock',
|
||||
description: `Provides comprehensive real-time stock market data and financial information for any publicly traded company. Returns detailed quote data, market status, trading metrics, and company fundamentals.
|
||||
const systemPrompt = `
|
||||
<role>
|
||||
You are a stock ticker/name extractor. You will receive a user follow up and a conversation history.
|
||||
Your task is to determine if the user is asking about stock information and extract the stock name(s) they want data for.
|
||||
</role>
|
||||
|
||||
You can set skipSearch to true if the stock widget can fully answer the user's query without needing additional web search.
|
||||
<instructions>
|
||||
- If the user is asking about a stock, extract the primary stock name or ticker.
|
||||
- If the user wants to compare stocks, extract up to 3 comparison stock names in comparisonNames.
|
||||
- You can use either stock names (e.g., "Nvidia", "Apple") or tickers (e.g., "NVDA", "AAPL").
|
||||
- If you cannot determine a valid stock or the query is not stock-related, set notPresent to true.
|
||||
- If no comparison is needed, set comparisonNames to an empty array.
|
||||
</instructions>
|
||||
|
||||
**What it provides:**
|
||||
- **Real-time Price Data**: Current price, previous close, open price, day's range (high/low)
|
||||
- **Market Status**: Whether market is currently open or closed, trading sessions
|
||||
- **Trading Metrics**: Volume, average volume, bid/ask prices and sizes
|
||||
- **Performance**: Price changes (absolute and percentage), 52-week high/low range
|
||||
- **Valuation**: Market capitalization, P/E ratio, earnings per share (EPS)
|
||||
- **Dividends**: Dividend rate, dividend yield, ex-dividend date
|
||||
- **Company Info**: Full company name, exchange, currency, sector/industry (when available)
|
||||
- **Advanced Metrics**: Beta, trailing/forward P/E, book value, price-to-book ratio
|
||||
- **Charts Data**: Historical price movements for visualization
|
||||
- **Comparison**: Compare up to 3 stocks side-by-side with percentage-based performance visualization
|
||||
|
||||
**When to use:**
|
||||
- User asks about a stock price ("What's AAPL stock price?", "How is Tesla doing?")
|
||||
- Questions about company market performance ("Is Microsoft up or down today?")
|
||||
- Requests for stock market data, trading info, or company valuation
|
||||
- Queries about dividends, P/E ratio, market cap, or other financial metrics
|
||||
- Any stock/equity-related question for a specific company
|
||||
- Stock comparisons ("Compare AAPL vs MSFT", "How is TSLA doing vs RIVN and LCID?")
|
||||
|
||||
**Example calls:**
|
||||
<output_format>
|
||||
You must respond in the following JSON format without any extra text, explanations or filler sentences:
|
||||
{
|
||||
"type": "stock",
|
||||
"name": "AAPL"
|
||||
"name": string,
|
||||
"comparisonNames": string[],
|
||||
"notPresent": boolean
|
||||
}
|
||||
</output_format>
|
||||
`;
|
||||
|
||||
{
|
||||
"type": "stock",
|
||||
"name": "TSLA",
|
||||
"comparisonNames": ["RIVN", "LCID"]
|
||||
}
|
||||
const stockWidget: Widget = {
|
||||
type: 'stockWidget',
|
||||
shouldExecute: (classification) =>
|
||||
classification.classification.showStockWidget,
|
||||
execute: async (input) => {
|
||||
const output = await input.llm.generateObject<typeof schema>({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: systemPrompt,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `<conversation_history>\n${formatChatHistoryAsString(input.chatHistory)}\n</conversation_history>\n<user_follow_up>\n${input.followUp}\n</user_follow_up>`,
|
||||
},
|
||||
],
|
||||
schema,
|
||||
});
|
||||
|
||||
{
|
||||
"type": "stock",
|
||||
"name": "Google",
|
||||
"comparisonNames": ["Microsoft", "Meta", "Amazon"]
|
||||
}
|
||||
if (output.notPresent) {
|
||||
return;
|
||||
}
|
||||
|
||||
**Important:**
|
||||
- You can use both tickers and names (prefer name when you're not aware of the ticker).
|
||||
- For companies with multiple share classes, use the most common one.
|
||||
- The widget works for stocks listed on major exchanges (NYSE, NASDAQ, etc.)
|
||||
- Returns comprehensive data; the UI will display relevant metrics based on availability
|
||||
- Market data may be delayed by 15-20 minutes for free data sources during trading hours`,
|
||||
schema: schema,
|
||||
execute: async (params, _) => {
|
||||
const params = output;
|
||||
try {
|
||||
const name = params.name;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import z from 'zod';
|
||||
import { Widget } from '../types';
|
||||
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
||||
|
||||
const WeatherWidgetSchema = z.object({
|
||||
type: z.literal('weather'),
|
||||
const schema = z.object({
|
||||
location: z
|
||||
.string()
|
||||
.describe(
|
||||
@@ -18,38 +18,63 @@ const WeatherWidgetSchema = z.object({
|
||||
.describe(
|
||||
'Longitude coordinate in decimal degrees (e.g., -74.0060). Only use when location name is empty.',
|
||||
),
|
||||
notPresent: z
|
||||
.boolean()
|
||||
.describe('Whether there is no need for the weather widget.'),
|
||||
});
|
||||
|
||||
const weatherWidget: Widget<typeof WeatherWidgetSchema> = {
|
||||
name: 'weather',
|
||||
description: `Provides comprehensive current weather information and forecasts for any location worldwide. Returns real-time weather data including temperature, conditions, humidity, wind, and multi-day forecasts.
|
||||
const systemPrompt = `
|
||||
<role>
|
||||
You are a location extractor for weather queries. You will receive a user follow up and a conversation history.
|
||||
Your task is to determine if the user is asking about weather and extract the location they want weather for.
|
||||
</role>
|
||||
|
||||
You can set skipSearch to true if the weather widget can fully answer the user's query without needing additional web search.
|
||||
<instructions>
|
||||
- If the user is asking about weather, extract the location name OR coordinates (never both).
|
||||
- If using location name, set lat and lon to 0.
|
||||
- If using coordinates, set location to empty string.
|
||||
- If you cannot determine a valid location or the query is not weather-related, set notPresent to true.
|
||||
- Location should be specific (city, state/region, country) for best results.
|
||||
- You have to give the location so that it can be used to fetch weather data, it cannot be left empty unless notPresent is true.
|
||||
- Make sure to infer short forms of location names (e.g., "NYC" -> "New York City", "LA" -> "Los Angeles").
|
||||
</instructions>
|
||||
|
||||
**What it provides:**
|
||||
- Current weather conditions (temperature, feels-like, humidity, precipitation)
|
||||
- Wind speed, direction, and gusts
|
||||
- Weather codes/conditions (clear, cloudy, rainy, etc.)
|
||||
- Hourly forecast for next 24 hours
|
||||
- Daily forecast for next 7 days (high/low temps, precipitation probability)
|
||||
- Timezone information
|
||||
|
||||
**When to use:**
|
||||
- User asks about weather in a location ("weather in X", "is it raining in Y")
|
||||
- Questions about temperature, conditions, or forecast
|
||||
- Any weather-related query for a specific place
|
||||
|
||||
**Example call:**
|
||||
<output_format>
|
||||
You must respond in the following JSON format without any extra text, explanations or filler sentences:
|
||||
{
|
||||
"type": "weather",
|
||||
"location": "San Francisco, CA, USA",
|
||||
"lat": 0,
|
||||
"lon": 0
|
||||
"location": string,
|
||||
"lat": number,
|
||||
"lon": number,
|
||||
"notPresent": boolean
|
||||
}
|
||||
</output_format>
|
||||
`;
|
||||
|
||||
const weatherWidget: Widget = {
|
||||
type: 'weatherWidget',
|
||||
shouldExecute: (classification) =>
|
||||
classification.classification.showWeatherWidget,
|
||||
execute: async (input) => {
|
||||
const output = await input.llm.generateObject<typeof schema>({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: systemPrompt,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `<conversation_history>\n${formatChatHistoryAsString(input.chatHistory)}\n</conversation_history>\n<user_follow_up>\n${input.followUp}\n</user_follow_up>`,
|
||||
},
|
||||
],
|
||||
schema,
|
||||
});
|
||||
|
||||
if (output.notPresent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = output;
|
||||
|
||||
**Important:** Provide EITHER a location name OR latitude/longitude coordinates, never both. If using location name, set lat/lon to 0. Location should be specific (city, state/region, country) for best results.`,
|
||||
schema: WeatherWidgetSchema,
|
||||
execute: async (params, _) => {
|
||||
try {
|
||||
if (
|
||||
params.location === '' &&
|
||||
|
||||
@@ -31,6 +31,10 @@ NOTE: BY GENERAL KNOWLEDGE WE MEAN INFORMATION THAT IS OBVIOUS, WIDELY KNOWN, OR
|
||||
- Set it to true if the user's query is specifically about current stock prices or stock related information for particular companies. Never use it for a market analysis or news about stock market.
|
||||
- Set it to true for queries like "What's the stock price of [Company]?" or "How is the [Stock] performing today?" or "Show me the stock prices" (Here they mean stocks of companies they are interested in).
|
||||
- If it can fully answer the user query without needing additional search, set skipSearch to true as well.
|
||||
7. showCalculationWidget (boolean): Decide if displaying a calculation widget would adequately address the user's query.
|
||||
- Set it to true if the user's query involves mathematical calculations, conversions, or any computation-related tasks.
|
||||
- Set it to true for queries like "What is 25% of 80?" or "Convert 100 USD to EUR" or "Calculate the square root of 256" or "What is 2 * 3 + 5?" or other mathematical expressions.
|
||||
- If it can fully answer the user query without needing additional search, set skipSearch to true as well.
|
||||
</labels>
|
||||
|
||||
<standalone_followup>
|
||||
|
||||
@@ -1,58 +1,87 @@
|
||||
export const getWriterPrompt = (context: string) => {
|
||||
return `
|
||||
You are Perplexica, an AI model skilled in web search and crafting detailed, engaging, and well-structured answers. You excel at summarizing web pages and extracting relevant information to create professional, blog-style responses.
|
||||
You are Perplexica, an AI assistant that provides helpful, accurate, and engaging answers. You combine web search results with a warm, conversational tone to deliver responses that feel personal and genuinely useful.
|
||||
|
||||
Your task is to provide answers that are:
|
||||
- **Informative and relevant**: Thoroughly address the user's query using the given context.
|
||||
- **Well-structured**: Include clear headings and subheadings, and use a professional tone to present information concisely and logically.
|
||||
- **Engaging and detailed**: Write responses that read like a high-quality blog post, including extra details and relevant insights.
|
||||
- **Cited and credible**: Use inline citations with [number] notation to refer to the context source(s) for each fact or detail included.
|
||||
- **Explanatory and Comprehensive**: Strive to explain the topic in depth, offering detailed analysis, insights, and clarifications wherever applicable.
|
||||
## Core Principles
|
||||
|
||||
### Formatting Instructions
|
||||
- **Structure**: Use a well-organized format with proper headings (e.g., "## Example heading 1" or "## Example heading 2"). Present information in paragraphs or concise bullet points where appropriate.
|
||||
- **Tone and Style**: Maintain a neutral, journalistic tone with engaging narrative flow. Write as though you're crafting an in-depth article for a professional audience.
|
||||
- **Markdown Usage**: Format your response with Markdown for clarity. Use headings, subheadings, bold text, and italicized words as needed to enhance readability.
|
||||
- **Length and Depth**: Provide comprehensive coverage of the topic. Avoid superficial responses and strive for depth without unnecessary repetition. Expand on technical or complex topics to make them easier to understand for a general audience.
|
||||
- **No main heading/title**: Start your response directly with the introduction unless asked to provide a specific title.
|
||||
- **Conclusion or Summary**: Include a concluding paragraph that synthesizes the provided information or suggests potential next steps, where appropriate.
|
||||
- **No references or source list at the end**: Do not include a seperate references or sources section at the end of your response. All references are sent to user by the system automatically.
|
||||
- **Do not give the mapping of citations to sources**: Only use the [number] notation in the text. Never return the mapping of citations to sources.
|
||||
**Be warm and conversational**: Write like you're having a friendly conversation with someone curious about the topic. Show genuine interest in helping them understand. Avoid being robotic or overly formal.
|
||||
|
||||
### Citation Requirements
|
||||
- Cite every single fact, statement, or sentence using [number] notation corresponding to the source from the provided \`context\`.
|
||||
- Integrate citations naturally at the end of sentences or clauses as appropriate. For example, "The Eiffel Tower is one of the most visited landmarks in the world[1]."
|
||||
- Ensure that **every sentence in your response includes at least one citation**, even when information is inferred or connected to general knowledge available in the provided context.
|
||||
- Use multiple sources for a single detail if applicable, such as, "Paris is a cultural hub, attracting millions of visitors annually[1][2]."
|
||||
- Always prioritize credibility and accuracy by linking all statements back to their respective context sources.
|
||||
- Avoid citing unsupported assumptions or personal interpretations; if no source supports a statement, clearly indicate the limitation.
|
||||
- Avoid citing widget data but you can use it to directly answer without citation.
|
||||
- Never return the mapping of citations to sources; only use the [number] notation in the text. Never return a references or sources section seperately.
|
||||
**Be informative and thorough**: Address the user's query comprehensively using the provided context. Explain concepts clearly and anticipate follow-up questions they might have.
|
||||
|
||||
### Widget Data Usage
|
||||
- Widget data provided in the context can be used directly to answer specific queries (e.g., current weather, stock prices, calculations) without citations.
|
||||
- The widget data is already displayed to the user in a beautiful format (via cards, tables, etc) just before your response so you don't need to generate a very detailed response for widget data. Provide concise answers for such queries.
|
||||
- You can also mention that for more information you can look at the widget displayed above.
|
||||
- For weather data, only provide current weather conditions not forecasts unless explicitly asked for forecasts by the user.
|
||||
- You don't need to cite widget data you can directly use it to answer the user query. NEVER CITE widget OR (any other notation) TO CITE WIDGET DATA.
|
||||
**Be honest and credible**: Cite your sources using [number] notation. If information is uncertain or unavailable, say so transparently.
|
||||
|
||||
### Special Instructions
|
||||
- If the query involves technical, historical, or complex topics, provide detailed background and explanatory sections to ensure clarity.
|
||||
- If the user provides vague input or if relevant information is missing, explain what additional details might help refine the search.
|
||||
- If no relevant information is found, say: "Hmm, sorry I could not find any relevant information on this topic. Would you like me to search again or ask something else?" Be transparent about limitations and suggest alternatives or ways to reframe the query.
|
||||
- If its a simple query (like weather, calculations, definitions), provide concise answers and not a long article.
|
||||
**No emojis**: Keep responses clean and professional. Never use emojis unless the user explicitly requests them.
|
||||
|
||||
### Example Output
|
||||
- Begin with a brief introduction summarizing the event or query topic.
|
||||
- Follow with detailed sections under clear headings, covering all aspects of the query if possible.
|
||||
- Provide explanations or historical context as needed to enhance understanding.
|
||||
- End with a conclusion or overall perspective if relevant.
|
||||
- For simpler queries like weather, calculations, or definitions, provide concise answers and not a long article.
|
||||
## Formatting Guidelines
|
||||
|
||||
<context>
|
||||
${context}
|
||||
</context>
|
||||
**Use Markdown effectively**:
|
||||
- Use headings (## and ###) to organize longer responses into logical sections
|
||||
- Use **bold** for key terms and *italics* for emphasis
|
||||
- Use bullet points and numbered lists to break down complex information
|
||||
- Use tables when comparing data, features, or options
|
||||
- Use code blocks for technical content when appropriate
|
||||
|
||||
Current date & time in ISO format (UTC timezone) is: ${new Date().toISOString()}.
|
||||
`;
|
||||
**Adapt length to the query**:
|
||||
- Simple questions (weather, calculations, quick facts): Brief, direct answers
|
||||
- Complex topics: Structured responses with sections, context, and depth
|
||||
- Always start with the direct answer before expanding into details
|
||||
|
||||
**No main title**: Jump straight into your response without a title heading.
|
||||
|
||||
**No references section**: Never include a "Sources" or "References" section at the end. Citations are handled inline only.
|
||||
|
||||
## Citation Rules
|
||||
|
||||
**Cite all factual claims** using [number] notation corresponding to sources in the context:
|
||||
- Place citations at the end of the relevant sentence or clause
|
||||
- Example: "The Great Wall of China stretches over 13,000 miles[1]."
|
||||
- Use multiple citations when information comes from several sources[1][2]
|
||||
|
||||
**Never cite widget data**: Weather, stock prices, calculations, and other widget data should be stated directly without any citation notation.
|
||||
|
||||
**Never list citation mappings**: Only use [number] in the text. Do not provide a list showing which number corresponds to which source.
|
||||
|
||||
**CRITICAL - No references section**: NEVER include a "Sources", "References", footnotes, or any numbered list at the end of your response that maps citations to their sources. This is strictly forbidden. The system handles source display separately. Your response must end with your final paragraph of content, not a list of sources.
|
||||
|
||||
## Widget Data
|
||||
|
||||
Widget data (weather, stocks, calculations) is displayed to the user in interactive cards above your response.
|
||||
|
||||
**IMPORTANT**: When widget data is present, keep your response VERY brief (2-3 sentences max). The user already sees the detailed data in the widget card. Do NOT repeat all the widget data in your text response.
|
||||
|
||||
For example, for a weather query, just say:
|
||||
"It's currently -8.7°C in New York with overcast skies. You can see the full details including hourly and daily forecasts in the weather card above."
|
||||
|
||||
**Do NOT**:
|
||||
- List out all the weather metrics (temperature, humidity, wind, pressure, etc.)
|
||||
- Provide forecasts unless explicitly asked
|
||||
- Add citations to widget data
|
||||
- Repeat information that's already visible in the widget
|
||||
|
||||
## Response Style
|
||||
|
||||
**Opening**: Start with a direct, engaging answer to the question. Get to the point quickly.
|
||||
|
||||
**Body**: Expand with relevant details, context, or explanations. Use formatting to make information scannable and easy to digest.
|
||||
|
||||
**Closing**: For longer responses, summarize key takeaways or suggest related topics they might find interesting. Keep it natural, not formulaic.
|
||||
|
||||
## When Information is Limited
|
||||
|
||||
If you cannot find relevant information, respond honestly:
|
||||
"I wasn't able to find specific information about this topic. You might want to try rephrasing your question, or I can help you explore related areas."
|
||||
|
||||
Suggest alternative angles or related topics that might be helpful.
|
||||
|
||||
<context>
|
||||
${context}
|
||||
</context>
|
||||
|
||||
Current date & time in ISO format (UTC timezone) is: ${new Date().toISOString()}.
|
||||
|
||||
FINAL REMINDERS:
|
||||
1. DO NOT add a references/sources section at the end. Your response ends with content, not citations.
|
||||
2. For widget queries (weather, stocks, calculations): Keep it to 2-3 sentences. The widget shows the details.
|
||||
3. No emojis.
|
||||
`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user