rogerserper
commited on
Added Serper.dev API as an alternative web search provider (#302)
Browse files
.env
CHANGED
|
@@ -8,7 +8,8 @@ MONGODB_DIRECT_CONNECTION=false
|
|
| 8 |
COOKIE_NAME=hf-chat
|
| 9 |
HF_ACCESS_TOKEN=#hf_<token> from from https://huggingface.co/settings/token
|
| 10 |
|
| 11 |
-
# used to activate search with web functionality. disabled if
|
|
|
|
| 12 |
SERPAPI_KEY=#your serpapi key here
|
| 13 |
|
| 14 |
# Parameters to enable "Sign in with HF"
|
|
|
|
| 8 |
COOKIE_NAME=hf-chat
|
| 9 |
HF_ACCESS_TOKEN=#hf_<token> from from https://huggingface.co/settings/token
|
| 10 |
|
| 11 |
+
# used to activate search with web functionality. disabled if none are defined. choose one of the following:
|
| 12 |
+
SERPER_API_KEY=#your serper.dev api key here
|
| 13 |
SERPAPI_KEY=#your serpapi key here
|
| 14 |
|
| 15 |
# Parameters to enable "Sign in with HF"
|
src/lib/server/websearch/searchWeb.ts
CHANGED
|
@@ -1,10 +1,53 @@
|
|
| 1 |
-
import { SERPAPI_KEY } from "$env/static/private";
|
| 2 |
|
| 3 |
import { getJson } from "serpapi";
|
| 4 |
import type { GoogleParameters } from "serpapi";
|
| 5 |
|
| 6 |
// Show result as JSON
|
| 7 |
export async function searchWeb(query: string) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
const params = {
|
| 9 |
q: query,
|
| 10 |
hl: "en",
|
|
|
|
| 1 |
+
import { SERPAPI_KEY, SERPER_API_KEY } from "$env/static/private";
|
| 2 |
|
| 3 |
import { getJson } from "serpapi";
|
| 4 |
import type { GoogleParameters } from "serpapi";
|
| 5 |
|
| 6 |
// Show result as JSON
|
| 7 |
export async function searchWeb(query: string) {
|
| 8 |
+
if (SERPER_API_KEY) {
|
| 9 |
+
return await searchWebSerper(query);
|
| 10 |
+
}
|
| 11 |
+
if (SERPAPI_KEY) {
|
| 12 |
+
return await searchWebSerpApi(query);
|
| 13 |
+
}
|
| 14 |
+
throw new Error("No Serper.dev or SerpAPI key found");
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export async function searchWebSerper(query: string) {
|
| 18 |
+
const params = {
|
| 19 |
+
q: query,
|
| 20 |
+
hl: "en",
|
| 21 |
+
gl: "us",
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
const response = await fetch("https://google.serper.dev/search", {
|
| 25 |
+
method: "POST",
|
| 26 |
+
body: JSON.stringify(params),
|
| 27 |
+
headers: {
|
| 28 |
+
"x-api-key": SERPER_API_KEY,
|
| 29 |
+
"Content-type": "application/json; charset=UTF-8",
|
| 30 |
+
},
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 34 |
+
const data = (await response.json()) as Record<string, any>;
|
| 35 |
+
|
| 36 |
+
if (!response.ok) {
|
| 37 |
+
throw new Error(
|
| 38 |
+
data["message"] ??
|
| 39 |
+
`Serper API returned error code ${response.status} - ${response.statusText}`
|
| 40 |
+
);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
return {
|
| 44 |
+
organic_results: data["organic"] ?? [],
|
| 45 |
+
knowledge_graph: data["knowledgeGraph"] ?? null,
|
| 46 |
+
answer_box: data["answerBox"] ?? null,
|
| 47 |
+
};
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
export async function searchWebSerpApi(query: string) {
|
| 51 |
const params = {
|
| 52 |
q: query,
|
| 53 |
hl: "en",
|
src/lib/types/WebSearch.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface WebSearch extends Timestamps {
|
|
| 12 |
searchQuery: string;
|
| 13 |
results: string[];
|
| 14 |
knowledgeGraph: string;
|
|
|
|
| 15 |
summary: string;
|
| 16 |
|
| 17 |
messages: WebSearchMessage[];
|
|
|
|
| 12 |
searchQuery: string;
|
| 13 |
results: string[];
|
| 14 |
knowledgeGraph: string;
|
| 15 |
+
answerBox: string;
|
| 16 |
summary: string;
|
| 17 |
|
| 18 |
messages: WebSearchMessage[];
|
src/routes/+layout.server.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { UrlDependency } from "$lib/types/UrlDependency";
|
|
| 6 |
import { defaultModel, models, oldModels, validateModel } from "$lib/server/models";
|
| 7 |
import { authCondition, requiresUser } from "$lib/server/auth";
|
| 8 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
| 9 |
-
import { SERPAPI_KEY } from "$env/static/private";
|
| 10 |
|
| 11 |
export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
|
| 12 |
const { conversations } = collections;
|
|
@@ -61,7 +61,7 @@ export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
|
|
| 61 |
DEFAULT_SETTINGS.shareConversationsWithModelAuthors,
|
| 62 |
ethicsModalAcceptedAt: settings?.ethicsModalAcceptedAt ?? null,
|
| 63 |
activeModel: settings?.activeModel ?? DEFAULT_SETTINGS.activeModel,
|
| 64 |
-
searchEnabled: !!SERPAPI_KEY,
|
| 65 |
},
|
| 66 |
models: models.map((model) => ({
|
| 67 |
id: model.id,
|
|
|
|
| 6 |
import { defaultModel, models, oldModels, validateModel } from "$lib/server/models";
|
| 7 |
import { authCondition, requiresUser } from "$lib/server/auth";
|
| 8 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
| 9 |
+
import { SERPAPI_KEY, SERPER_API_KEY } from "$env/static/private";
|
| 10 |
|
| 11 |
export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
|
| 12 |
const { conversations } = collections;
|
|
|
|
| 61 |
DEFAULT_SETTINGS.shareConversationsWithModelAuthors,
|
| 62 |
ethicsModalAcceptedAt: settings?.ethicsModalAcceptedAt ?? null,
|
| 63 |
activeModel: settings?.activeModel ?? DEFAULT_SETTINGS.activeModel,
|
| 64 |
+
searchEnabled: !!(SERPAPI_KEY || SERPER_API_KEY),
|
| 65 |
},
|
| 66 |
models: models.map((model) => ({
|
| 67 |
id: model.id,
|
src/routes/conversation/[id]/web-search/+server.ts
CHANGED
|
@@ -50,6 +50,7 @@ export async function GET({ params, locals, url }) {
|
|
| 50 |
prompt: prompt,
|
| 51 |
searchQuery: "",
|
| 52 |
knowledgeGraph: "",
|
|
|
|
| 53 |
results: [],
|
| 54 |
summary: "",
|
| 55 |
messages: [],
|
|
@@ -79,7 +80,12 @@ export async function GET({ params, locals, url }) {
|
|
| 79 |
results.organic_results.map((el: { link: string }) => el.link)) ??
|
| 80 |
[];
|
| 81 |
|
| 82 |
-
if (results.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
// if google returns a knowledge graph, we use it
|
| 84 |
webSearch.knowledgeGraph = JSON.stringify(removeLinks(results.knowledge_graph));
|
| 85 |
text = webSearch.knowledgeGraph;
|
|
|
|
| 50 |
prompt: prompt,
|
| 51 |
searchQuery: "",
|
| 52 |
knowledgeGraph: "",
|
| 53 |
+
answerBox: "",
|
| 54 |
results: [],
|
| 55 |
summary: "",
|
| 56 |
messages: [],
|
|
|
|
| 80 |
results.organic_results.map((el: { link: string }) => el.link)) ??
|
| 81 |
[];
|
| 82 |
|
| 83 |
+
if (results.answer_box) {
|
| 84 |
+
// if google returns an answer box, we use it
|
| 85 |
+
webSearch.answerBox = JSON.stringify(removeLinks(results.answer_box));
|
| 86 |
+
text = webSearch.answerBox;
|
| 87 |
+
appendUpdate("Found a Google answer box");
|
| 88 |
+
} else if (results.knowledge_graph) {
|
| 89 |
// if google returns a knowledge graph, we use it
|
| 90 |
webSearch.knowledgeGraph = JSON.stringify(removeLinks(results.knowledge_graph));
|
| 91 |
text = webSearch.knowledgeGraph;
|