Compare commits

...

13 Commits

Author SHA1 Message Date
ItzCrazyKns
2e2c584169 Merge branch 'feat/improve-search-architecture' of https://github.com/ItzCrazyKns/Perplexica into feat/improve-search-architecture 2025-12-27 13:34:10 +05:30
Kushagra Srivastava
7f3f881964 Update src/components/Navbar.tsx
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2025-12-27 13:32:20 +05:30
ItzCrazyKns
a755867e87 Update MessageSources.tsx 2025-12-27 13:30:59 +05:30
Kushagra Srivastava
9620e63e3f Update src/components/MessageActions/Copy.tsx
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2025-12-27 13:29:43 +05:30
ItzCrazyKns
ec5ff6f4a8 Update plan.ts 2025-12-27 13:26:07 +05:30
ItzCrazyKns
0ace778b03 Merge branch 'feat/improve-search-architecture' of https://github.com/ItzCrazyKns/Perplexica into feat/improve-search-architecture 2025-12-27 13:24:50 +05:30
ItzCrazyKns
6919ad1a0f feat(app): address review 2025-12-27 13:24:35 +05:30
Kushagra Srivastava
b5ba8c48c0 Update src/components/WeatherWidget.tsx
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2025-12-27 13:14:46 +05:30
ItzCrazyKns
65fdecb122 feat(docs): update architecture docs 2025-12-27 13:09:11 +05:30
ItzCrazyKns
5a44319d85 feat(guides): update contributing guides 2025-12-27 13:09:01 +05:30
ItzCrazyKns
cc183cd0cd feat(readme): update features & upcoming features 2025-12-27 13:08:28 +05:30
ItzCrazyKns
50ca7ac73a feat(api): update search api & related documentation 2025-12-27 13:07:59 +05:30
ItzCrazyKns
a31a4ab295 feat(agents): add api search agent 2025-12-27 13:07:42 +05:30
14 changed files with 364 additions and 146 deletions

View File

@@ -11,33 +11,63 @@ Perplexica's codebase is organized as follows:
- **UI Components and Pages**:
- **Components (`src/components`)**: Reusable UI components.
- **Pages and Routes (`src/app`)**: Next.js app directory structure with page components.
- Main app routes include: home (`/`), chat (`/c`), discover (`/discover`), library (`/library`), and settings (`/settings`).
- **API Routes (`src/app/api`)**: API endpoints implemented with Next.js API routes.
- `/api/chat`: Handles chat interactions.
- `/api/search`: Provides direct access to Perplexica's search capabilities.
- Other endpoints for models, files, and suggestions.
- Main app routes include: home (`/`), chat (`/c`), discover (`/discover`), and library (`/library`).
- **API Routes (`src/app/api`)**: Server endpoints implemented with Next.js route handlers.
- **Backend Logic (`src/lib`)**: Contains all the backend functionality including search, database, and API logic.
- The search functionality is present inside `src/lib/search` directory.
- All of the focus modes are implemented using the Meta Search Agent class in `src/lib/search/metaSearchAgent.ts`.
- The search system lives in `src/lib/agents/search`.
- The search pipeline is split into classification, research, widgets, and writing.
- Database functionality is in `src/lib/db`.
- Chat model and embedding model providers are managed in `src/lib/providers`.
- Prompt templates and LLM chain definitions are in `src/lib/prompts` and `src/lib/chains` respectively.
- Chat model and embedding model providers are in `src/lib/models/providers`, and models are loaded via `src/lib/models/registry.ts`.
- Prompt templates are in `src/lib/prompts`.
- SearXNG integration is in `src/lib/searxng.ts`.
- Upload search lives in `src/lib/uploads`.
### Where to make changes
If you are not sure where to start, use this section as a map.
- **Search behavior and reasoning**
- `src/lib/agents/search` contains the core chat and search pipeline.
- `classifier.ts` decides whether research is needed and what should run.
- `researcher/` gathers information in the background.
- **Add or change a search capability**
- Research tools (web, academic, discussions, uploads, scraping) live in `src/lib/agents/search/researcher/actions`.
- Tools are registered in `src/lib/agents/search/researcher/actions/index.ts`.
- **Add or change widgets**
- Widgets live in `src/lib/agents/search/widgets`.
- Widgets run in parallel with research and show structured results in the UI.
- **Model integrations**
- Providers live in `src/lib/models/providers`.
- Add new providers there and wire them into the model registry so they show up in the app.
- **Architecture docs**
- High level overview: `docs/architecture/README.md`
- High level flow: `docs/architecture/WORKING.md`
## API Documentation
Perplexica exposes several API endpoints for programmatic access, including:
Perplexica includes API documentation for programmatic access.
- **Search API**: Access Perplexica's advanced search capabilities directly via the `/api/search` endpoint. For detailed documentation, see `docs/api/search.md`.
- **Search API**: For detailed documentation, see `docs/API/SEARCH.md`.
## Setting Up Your Environment
Before diving into coding, setting up your local environment is key. Here's what you need to do:
1. In the root directory, locate the `sample.config.toml` file.
2. Rename it to `config.toml` and fill in the necessary configuration fields.
3. Run `npm install` to install all dependencies.
4. Run `npm run db:migrate` to set up the local sqlite database.
5. Use `npm run dev` to start the application in development mode.
1. Run `npm install` to install all dependencies.
2. Use `npm run dev` to start the application in development mode.
3. Open http://localhost:3000 and complete the setup in the UI (API keys, models, search backend URL, etc.).
Database migrations are applied automatically on startup.
For full installation options (Docker and non Docker), see the installation guide in the repository README.
**Please note**: Docker configurations are present for setting up production environments, whereas `npm run dev` is used for development purposes.

View File

@@ -18,9 +18,11 @@ Want to know more about its architecture and how it works? You can read it [here
🤖 **Support for all major AI providers** - Use local LLMs through Ollama or connect to OpenAI, Anthropic Claude, Google Gemini, Groq, and more. Mix and match models based on your needs.
**Smart search modes** - Choose Balanced Mode for everyday searches, Fast Mode when you need quick answers, or wait for Quality Mode (coming soon) for deep research.
**Smart search modes** - Choose Speed Mode when you need quick answers, Balanced Mode for everyday searches, or Quality Mode for deep research.
🎯 **Six specialized focus modes** - Get better results with modes designed for specific tasks: Academic papers, YouTube videos, Reddit discussions, Wolfram Alpha calculations, writing assistance, or general web search.
🧭 **Pick your sources** - Search the web, discussions, or academic papers. More sources and integrations are in progress.
🧩 **Widgets** - Helpful UI cards that show up when relevant, like weather, calculations, stock prices, and other quick lookups.
🔍 **Web search powered by SearxNG** - Access multiple search engines while keeping your identity private. Support for Tavily and Exa coming soon for even better results.
@@ -237,13 +239,8 @@ Perplexica runs on Next.js and handles all API requests. It works right away on
## Upcoming Features
- [x] Add settings page
- [x] Adding support for local LLMs
- [x] History Saving features
- [x] Introducing various Focus Modes
- [x] Adding API support
- [x] Adding Discover
- [ ] Finalizing Copilot Mode
- [] Adding more widgets, integrations, search sources
- [] Adding authentication
## Support Us

View File

@@ -57,7 +57,7 @@ Use the `id` field as the `providerId` and the `key` field from the models array
### Request
The API accepts a JSON object in the request body, where you define the focus mode, chat models, embedding models, and your query.
The API accepts a JSON object in the request body, where you define the enabled search `sources`, chat models, embedding models, and your query.
#### Request Body Structure
@@ -72,7 +72,7 @@ The API accepts a JSON object in the request body, where you define the focus mo
"key": "text-embedding-3-large"
},
"optimizationMode": "speed",
"focusMode": "webSearch",
"sources": ["web"],
"query": "What is Perplexica",
"history": [
["human", "Hi, how are you?"],
@@ -87,24 +87,25 @@ The API accepts a JSON object in the request body, where you define the focus mo
### Request Parameters
- **`chatModel`** (object, optional): Defines the chat model to be used for the query. To get available providers and models, send a GET request to `http://localhost:3000/api/providers`.
- **`chatModel`** (object, required): Defines the chat model to be used for the query. To get available providers and models, send a GET request to `http://localhost:3000/api/providers`.
- `providerId` (string): The UUID of the provider. You can get this from the `/api/providers` endpoint response.
- `key` (string): The model key/identifier (e.g., `gpt-4o-mini`, `llama3.1:latest`). Use the `key` value from the provider's `chatModels` array, not the display name.
- **`embeddingModel`** (object, optional): Defines the embedding model for similarity-based searching. To get available providers and models, send a GET request to `http://localhost:3000/api/providers`.
- **`embeddingModel`** (object, required): Defines the embedding model for similarity-based searching. To get available providers and models, send a GET request to `http://localhost:3000/api/providers`.
- `providerId` (string): The UUID of the embedding provider. You can get this from the `/api/providers` endpoint response.
- `key` (string): The embedding model key (e.g., `text-embedding-3-large`, `nomic-embed-text`). Use the `key` value from the provider's `embeddingModels` array, not the display name.
- **`focusMode`** (string, required): Specifies which focus mode to use. Available modes:
- **`sources`** (array, required): Which search sources to enable. Available values:
- `webSearch`, `academicSearch`, `writingAssistant`, `wolframAlphaSearch`, `youtubeSearch`, `redditSearch`.
- `web`, `academic`, `discussions`.
- **`optimizationMode`** (string, optional): Specifies the optimization mode to control the balance between performance and quality. Available modes:
- `speed`: Prioritize speed and return the fastest answer.
- `balanced`: Provide a balanced answer with good speed and reasonable quality.
- `quality`: Prioritize answer quality (may be slower).
- **`query`** (string, required): The search query or question.
@@ -132,14 +133,14 @@ The response from the API includes both the final message and the sources used t
"message": "Perplexica is an innovative, open-source AI-powered search engine designed to enhance the way users search for information online. Here are some key features and characteristics of Perplexica:\n\n- **AI-Powered Technology**: It utilizes advanced machine learning algorithms to not only retrieve information but also to understand the context and intent behind user queries, providing more relevant results [1][5].\n\n- **Open-Source**: Being open-source, Perplexica offers flexibility and transparency, allowing users to explore its functionalities without the constraints of proprietary software [3][10].",
"sources": [
{
"pageContent": "Perplexica is an innovative, open-source AI-powered search engine designed to enhance the way users search for information online.",
"content": "Perplexica is an innovative, open-source AI-powered search engine designed to enhance the way users search for information online.",
"metadata": {
"title": "What is Perplexica, and how does it function as an AI-powered search ...",
"url": "https://askai.glarity.app/search/What-is-Perplexica--and-how-does-it-function-as-an-AI-powered-search-engine"
}
},
{
"pageContent": "Perplexica is an open-source AI-powered search tool that dives deep into the internet to find precise answers.",
"content": "Perplexica is an open-source AI-powered search tool that dives deep into the internet to find precise answers.",
"metadata": {
"title": "Sahar Mor's Post",
"url": "https://www.linkedin.com/posts/sahar-mor_a-new-open-source-project-called-perplexica-activity-7204489745668694016-ncja"
@@ -158,7 +159,7 @@ Example of streamed response objects:
```
{"type":"init","data":"Stream connected"}
{"type":"sources","data":[{"pageContent":"...","metadata":{"title":"...","url":"..."}},...]}
{"type":"sources","data":[{"content":"...","metadata":{"title":"...","url":"..."}},...]}
{"type":"response","data":"Perplexica is an "}
{"type":"response","data":"innovative, open-source "}
{"type":"response","data":"AI-powered search engine..."}
@@ -174,9 +175,9 @@ Clients should process each line as a separate JSON object. The different messag
### Fields in the Response
- **`message`** (string): The search result, generated based on the query and focus mode.
- **`message`** (string): The search result, generated based on the query and enabled `sources`.
- **`sources`** (array): A list of sources that were used to generate the search result. Each source includes:
- `pageContent`: A snippet of the relevant content from the source.
- `content`: A snippet of the relevant content from the source.
- `metadata`: Metadata about the source, including:
- `title`: The title of the webpage.
- `url`: The URL of the webpage.
@@ -185,5 +186,5 @@ Clients should process each line as a separate JSON object. The different messag
If an error occurs during the search process, the API will return an appropriate error message with an HTTP status code.
- **400**: If the request is malformed or missing required fields (e.g., no focus mode or query).
- **400**: If the request is malformed or missing required fields (e.g., no `sources` or `query`).
- **500**: If an internal server error occurs during the search.

View File

@@ -1,11 +1,38 @@
# Perplexica's Architecture
# Perplexica Architecture
Perplexica's architecture consists of the following key components:
Perplexica is a Next.js application that combines an AI chat experience with search.
1. **User Interface**: A web-based interface that allows users to interact with Perplexica for searching images, videos, and much more.
2. **Agent/Chains**: These components predict Perplexica's next actions, understand user queries, and decide whether a web search is necessary.
3. **SearXNG**: A metadata search engine used by Perplexica to search the web for sources.
4. **LLMs (Large Language Models)**: Utilized by agents and chains for tasks like understanding content, writing responses, and citing sources. Examples include Claude, GPTs, etc.
5. **Embedding Models**: To improve the accuracy of search results, embedding models re-rank the results using similarity search algorithms such as cosine similarity and dot product distance.
For a high level flow, see [WORKING.md](WORKING.md). For deeper implementation details, see [CONTRIBUTING.md](../../CONTRIBUTING.md).
For a more detailed explanation of how these components work together, see [WORKING.md](https://github.com/ItzCrazyKns/Perplexica/tree/master/docs/architecture/WORKING.md).
## Key components
1. **User Interface**
- A web based UI that lets users chat, search, and view citations.
2. **API Routes**
- `POST /api/chat` powers the chat UI.
- `POST /api/search` provides a programmatic search endpoint.
- `GET /api/providers` lists available providers and model keys.
3. **Agents and Orchestration**
- The system classifies the question first.
- It can run research and widgets in parallel.
- It generates the final answer and includes citations.
4. **Search Backend**
- A meta search backend is used to fetch relevant web results when research is enabled.
5. **LLMs (Large Language Models)**
- Used for classification, writing answers, and producing citations.
6. **Embedding Models**
- Used for semantic search over user uploaded files.
7. **Storage**
- Chats and messages are stored so conversations can be reloaded.

View File

@@ -1,19 +1,72 @@
# How does Perplexica work?
# How Perplexica Works
Curious about how Perplexica works? Don't worry, we'll cover it here. Before we begin, make sure you've read about the architecture of Perplexica to ensure you understand what it's made up of. Haven't read it? You can read it [here](https://github.com/ItzCrazyKns/Perplexica/tree/master/docs/architecture/README.md).
This is a high level overview of how Perplexica answers a question.
We'll understand how Perplexica works by taking an example of a scenario where a user asks: "How does an A.C. work?". We'll break down the process into steps to make it easier to understand. The steps are as follows:
If you want a component level overview, see [README.md](README.md).
1. The message is sent to the `/api/chat` route where it invokes the chain. The chain will depend on your focus mode. For this example, let's assume we use the "webSearch" focus mode.
2. The chain is now invoked; first, the message is passed to another chain where it first predicts (using the chat history and the question) whether there is a need for sources and searching the web. If there is, it will generate a query (in accordance with the chat history) for searching the web that we'll take up later. If not, the chain will end there, and then the answer generator chain, also known as the response generator, will be started.
3. The query returned by the first chain is passed to SearXNG to search the web for information.
4. After the information is retrieved, it is based on keyword-based search. We then convert the information into embeddings and the query as well, then we perform a similarity search to find the most relevant sources to answer the query.
5. After all this is done, the sources are passed to the response generator. This chain takes all the chat history, the query, and the sources. It generates a response that is streamed to the UI.
If you want implementation details, see [CONTRIBUTING.md](../../CONTRIBUTING.md).
## How are the answers cited?
## What happens when you ask a question
The LLMs are prompted to do so. We've prompted them so well that they cite the answers themselves, and using some UI magic, we display it to the user.
When you send a message in the UI, the app calls `POST /api/chat`.
## Image and Video Search
At a high level, we do three things:
Image and video searches are conducted in a similar manner. A query is always generated first, then we search the web for images and videos that match the query. These results are then returned to the user.
1. Classify the question and decide what to do next.
2. Run research and widgets in parallel.
3. Write the final answer and include citations.
## Classification
Before searching or answering, we run a classification step.
This step decides things like:
- Whether we should do research for this question
- Whether we should show any widgets
- How to rewrite the question into a clearer standalone form
## Widgets
Widgets are small, structured helpers that can run alongside research.
Examples include weather, stocks, and simple calculations.
If a widget is relevant, we show it in the UI while the answer is still being generated.
Widgets are helpful context for the answer, but they are not part of what the model should cite.
## Research
If research is needed, we gather information in the background while widgets can run.
Depending on configuration, research may include web lookup and searching user uploaded files.
## Answer generation
Once we have enough context, the chat model generates the final response.
You can control the tradeoff between speed and quality using `optimizationMode`:
- `speed`
- `balanced`
- `quality`
## How citations work
We prompt the model to cite the references it used. The UI then renders those citations alongside the supporting links.
## Search API
If you are integrating Perplexica into another product, you can call `POST /api/search`.
It returns:
- `message`: the generated answer
- `sources`: supporting references used for the answer
You can also enable streaming by setting `stream: true`.
## Image and video search
Image and video search use separate endpoints (`POST /api/images` and `POST /api/videos`). We generate a focused query using the chat model, then fetch matching results from a search backend.

View File

@@ -28,8 +28,8 @@
"notNull": true,
"autoincrement": false
},
"focusMode": {
"name": "focusMode",
"sources": {
"name": "sources",
"type": "text",
"primaryKey": false,
"notNull": true,

View File

@@ -1,12 +1,13 @@
import ModelRegistry from '@/lib/models/registry';
import { ModelWithProvider } from '@/lib/models/types';
import SessionManager from '@/lib/session';
import SearchAgent from '@/lib/agents/search';
import { ChatTurnMessage } from '@/lib/types';
import { SearchSources } from '@/lib/agents/search/types';
import APISearchAgent from '@/lib/agents/search/api';
interface ChatRequestBody {
optimizationMode: 'speed' | 'balanced';
focusMode: string;
optimizationMode: 'speed' | 'balanced' | 'quality';
sources: SearchSources[];
chatModel: ModelWithProvider;
embeddingModel: ModelWithProvider;
query: string;
@@ -19,15 +20,15 @@ export const POST = async (req: Request) => {
try {
const body: ChatRequestBody = await req.json();
if (!body.focusMode || !body.query) {
if (!body.sources || !body.query) {
return Response.json(
{ message: 'Missing focus mode or query' },
{ message: 'Missing sources or query' },
{ status: 400 },
);
}
body.history = body.history || [];
body.optimizationMode = body.optimizationMode || 'balanced';
body.optimizationMode = body.optimizationMode || 'speed';
body.stream = body.stream || false;
const registry = new ModelRegistry();
@@ -48,18 +49,21 @@ export const POST = async (req: Request) => {
const session = SessionManager.createSession();
const agent = new SearchAgent();
const agent = new APISearchAgent();
agent.searchAsync(session, {
chatHistory: history,
config: {
embedding: embeddings,
llm: llm,
sources: ['web', 'discussions', 'academic'],
mode: 'balanced',
sources: body.sources,
mode: body.optimizationMode,
fileIds: [],
systemInstructions: body.systemInstructions || '',
},
followUp: body.query,
chatId: crypto.randomUUID(),
messageId: crypto.randomUUID(),
});
if (!body.stream) {
@@ -71,36 +75,37 @@ export const POST = async (req: Request) => {
let message = '';
let sources: any[] = [];
session.addListener('data', (data: string) => {
try {
const parsedData = JSON.parse(data);
if (parsedData.type === 'response') {
message += parsedData.data;
} else if (parsedData.type === 'sources') {
sources = parsedData.data;
session.subscribe((event: string, data: Record<string, any>) => {
if (event === 'data') {
try {
if (data.type === 'response') {
message += data.data;
} else if (data.type === 'searchResults') {
sources = data.data;
}
} catch (error) {
reject(
Response.json(
{ message: 'Error parsing data' },
{ status: 500 },
),
);
}
} catch (error) {
}
if (event === 'end') {
resolve(Response.json({ message, sources }, { status: 200 }));
}
if (event === 'error') {
reject(
Response.json(
{ message: 'Error parsing data' },
{ message: 'Search error', error: data },
{ status: 500 },
),
);
}
});
session.addListener('end', () => {
resolve(Response.json({ message, sources }, { status: 200 }));
});
session.addListener('error', (error: any) => {
reject(
Response.json(
{ message: 'Search error', error },
{ status: 500 },
),
);
});
},
);
}
@@ -131,54 +136,54 @@ export const POST = async (req: Request) => {
} catch (error) {}
});
session.addListener('data', (data: string) => {
if (signal.aborted) return;
session.subscribe((event: string, data: Record<string, any>) => {
if (event === 'data') {
if (signal.aborted) return;
try {
const parsedData = JSON.parse(data);
if (parsedData.type === 'response') {
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'response',
data: parsedData.data,
}) + '\n',
),
);
} else if (parsedData.type === 'sources') {
sources = parsedData.data;
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'sources',
data: sources,
}) + '\n',
),
);
try {
if (data.type === 'response') {
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'response',
data: data.data,
}) + '\n',
),
);
} else if (data.type === 'searchResults') {
sources = data.data;
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'sources',
data: sources,
}) + '\n',
),
);
}
} catch (error) {
controller.error(error);
}
} catch (error) {
controller.error(error);
}
});
session.addListener('end', () => {
if (signal.aborted) return;
if (event === 'end') {
if (signal.aborted) return;
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'done',
}) + '\n',
),
);
controller.close();
});
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'done',
}) + '\n',
),
);
controller.close();
}
session.addListener('error', (error: any) => {
if (signal.aborted) return;
if (event === 'error') {
if (signal.aborted) return;
controller.error(error);
controller.error(data);
}
});
},
cancel() {

View File

@@ -21,15 +21,16 @@ const Copy = ({
) as SourceBlock[];
const contentToCopy = `${initialMessage}${
sources.length > 0 &&
`\n\nCitations:\n${sources
.map((source) => source.data)
.flat()
.map(
(s, i) =>
`[${i + 1}] ${s.metadata.url.startsWith('file_id://') ? s.metadata.fileName || 'Uploaded File' : s.metadata.url}`,
)
.join(`\n`)}`
sources.length > 0
? `\n\nCitations:\n${sources
.map((source) => source.data)
.flat()
.map(
(s, i) =>
`[${i + 1}] ${s.metadata.url.startsWith('file_id://') ? s.metadata.fileName || 'Uploaded File' : s.metadata.url}`,
)
.join(`\n`)}`
: ''
}`;
navigator.clipboard.writeText(contentToCopy);

View File

@@ -70,7 +70,7 @@ const MessageSources = ({ sources }: { sources: Chunk[] }) => {
>
<div className="flex flex-row items-center space-x-1">
{sources.slice(3, 6).map((source, i) => {
return source.metadata.url === 'File' ? (
return source.metadata.includes('file_id://') ? (
<div
key={i}
className="bg-dark-200 hover:bg-dark-100 transition duration-200 flex items-center justify-center w-6 h-6 rounded-full"
@@ -124,7 +124,7 @@ const MessageSources = ({ sources }: { sources: Chunk[] }) => {
</p>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-center space-x-1">
{source.metadata.url === 'File' ? (
{source.metadata.url.includes('file_id://') ? (
<div className="bg-dark-200 hover:bg-dark-100 transition duration-200 flex items-center justify-center w-6 h-6 rounded-full">
<File size={12} className="text-white/70" />
</div>

View File

@@ -205,8 +205,9 @@ const Navbar = () => {
useEffect(() => {
if (sections.length > 0 && sections[0].message) {
const newTitle =
sections[0].message.query.substring(0, 30) + '...' ||
'New Conversation';
sections[0].message.query.length > 30
? `${sections[0].message.query.substring(0, 30).trim()}...`
: sections[0].message.query || 'New Conversation';
setTitle(newTitle);
const newTimeAgo = formatTimeDifference(

View File

@@ -91,7 +91,7 @@ const WeatherWidget = () => {
setData({
temperature: data.temperature,
condition: data.condition,
location: 'Mars',
location: location.city,
humidity: data.humidity,
windSpeed: data.windSpeed,
icon: data.icon,

View File

@@ -0,0 +1,99 @@
import { ResearcherOutput, SearchAgentInput } from './types';
import SessionManager from '@/lib/session';
import { classify } from './classifier';
import Researcher from './researcher';
import { getWriterPrompt } from '@/lib/prompts/search/writer';
import { WidgetExecutor } from './widgets';
class APISearchAgent {
async searchAsync(session: SessionManager, input: SearchAgentInput) {
const classification = await classify({
chatHistory: input.chatHistory,
enabledSources: input.config.sources,
query: input.followUp,
llm: input.config.llm,
});
const widgetPromise = WidgetExecutor.executeAll({
classification,
chatHistory: input.chatHistory,
followUp: input.followUp,
llm: input.config.llm,
});
let searchPromise: Promise<ResearcherOutput> | null = null;
if (!classification.classification.skipSearch) {
const researcher = new Researcher();
searchPromise = researcher.research(SessionManager.createSession(), {
chatHistory: input.chatHistory,
followUp: input.followUp,
classification: classification,
config: input.config,
});
}
const [widgetOutputs, searchResults] = await Promise.all([
widgetPromise,
searchPromise,
]);
if (searchResults) {
session.emit('data', {
type: 'searchResults',
data: searchResults.searchFindings,
});
}
session.emit('data', {
type: 'researchComplete',
});
const finalContext =
searchResults?.searchFindings
.map(
(f, index) =>
`<result index=${index + 1} title=${f.metadata.title}>${f.content}</result>`,
)
.join('\n') || '';
const widgetContext = widgetOutputs
.map((o) => {
return `<result>${o.llmContext}</result>`;
})
.join('\n-------------\n');
const finalContextWithWidgets = `<search_results note="These are the search results and assistant can cite these">\n${finalContext}\n</search_results>\n<widgets_result noteForAssistant="Its output is already showed to the user, assistant can use this information to answer the query but do not CITE this as a souce">\n${widgetContext}\n</widgets_result>`;
const writerPrompt = getWriterPrompt(
finalContextWithWidgets,
input.config.systemInstructions,
input.config.mode,
);
const answerStream = input.config.llm.streamText({
messages: [
{
role: 'system',
content: writerPrompt,
},
...input.chatHistory,
{
role: 'user',
content: input.followUp,
},
],
});
for await (const chunk of answerStream) {
session.emit('data', {
type: 'response',
data: chunk.contentChunk,
});
}
session.emit('end', {});
}
}
export default APISearchAgent;

View File

@@ -17,7 +17,7 @@ Here are some examples of good plans:
<examples>
- "Okay, the user wants to know the latest advancements in renewable energy. I will start by looking for recent articles and studies on this topic, then summarize the key points." -> "I have gathered enough information to provide a comprehensive answer."
- "The user is asking about the health benefits of a Mediterranean diet. I will search for scientific studies and expert opinions on this diet, then compile the findings into a clear summary." -> "I have gathered information about the Mediterranean diet and its health benefits, I will now look up for any recent studies to ensure the information is current."
<examples>
</examples>
YOU CAN NEVER CALL ANY OTHER TOOL BEFORE CALLING THIS ONE FIRST, IF YOU DO, THAT CALL WOULD BE IGNORED.
`;

View File

@@ -51,6 +51,10 @@ const calculationWidget: Widget = {
schema,
});
if (output.notPresent) {
return;
}
const result = mathEval(output.expression);
return {