matt HOFFNER commited on
Commit
f4e05b5
β€’
1 Parent(s): cb5d7d2

add serp api

Browse files
README.md CHANGED
@@ -7,17 +7,3 @@ app_port: 3000
7
 
8
  # πŸ”— url-surfer πŸ„β€β™‚οΈ
9
 
10
- Simple API to navigate to a URL from a prompt, returning relevant context from the prompt using a vector store.
11
-
12
- ## How it works
13
-
14
- 1. Navigate (parse url/fetch/mime-type check)
15
- 2. Extract text (jsdom/puppeteer/pdf-parse)
16
- 3. Create vector store (transformers.js)
17
- 4. Return vector results in prompt (langchain)
18
-
19
-
20
- ## Ideas
21
-
22
- * Configuration: Vector search size, token counting
23
- * OpenAI functions integration
 
7
 
8
  # πŸ”— url-surfer πŸ„β€β™‚οΈ
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/chat/route.ts CHANGED
@@ -1,8 +1,10 @@
1
  import { Configuration, OpenAIApi } from "openai-edge";
2
  import { OpenAIStream, StreamingTextResponse } from "ai";
3
  import { createUrlSurfer } from "@/app/tools/surfer";
 
4
 
5
- const [, requestSchema] = createUrlSurfer();
 
6
 
7
  const config = new Configuration({
8
  apiKey: process.env.OPENAI_API_KEY,
@@ -10,7 +12,8 @@ const config = new Configuration({
10
  const openai = new OpenAIApi(config);
11
 
12
  const functions: any[] = [
13
- requestSchema
 
14
  ];
15
 
16
  export async function POST(req: Request) {
 
1
  import { Configuration, OpenAIApi } from "openai-edge";
2
  import { OpenAIStream, StreamingTextResponse } from "ai";
3
  import { createUrlSurfer } from "@/app/tools/surfer";
4
+ import { createSerpApi } from "@/app/tools/serp-api";
5
 
6
+ const [, urlSurferSchema] = createUrlSurfer();
7
+ const [, serpApiSchema] = createSerpApi({ apiKey: process.env.SERP_API_KEY || '' });
8
 
9
  const config = new Configuration({
10
  apiKey: process.env.OPENAI_API_KEY,
 
12
  const openai = new OpenAIApi(config);
13
 
14
  const functions: any[] = [
15
+ urlSurferSchema,
16
+ serpApiSchema
17
  ];
18
 
19
  export async function POST(req: Request) {
app/page.tsx CHANGED
@@ -15,13 +15,15 @@ const Page: React.FC = () => {
15
  functionCall,
16
  ) => {
17
  let result;
18
- const response = await fetch("/api/surfer", {
 
19
  method: "POST",
20
  headers: {
21
  "Content-Type": "application/json",
22
  },
23
  body: JSON.stringify({
24
- prompt: input
 
25
  })
26
  } as any);
27
 
 
15
  functionCall,
16
  ) => {
17
  let result;
18
+ const { name, arguments: args } = functionCall;
19
+ const response = await fetch("/api/functions", {
20
  method: "POST",
21
  headers: {
22
  "Content-Type": "application/json",
23
  },
24
  body: JSON.stringify({
25
+ prompt: input,
26
+ name: name
27
  })
28
  } as any);
29
 
app/tools/serp-api.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Tool } from 'openai-function-calling-tools';
2
+ import { z } from 'zod';
3
+ import { getJson } from 'serpapi';
4
+
5
+ function createSerpApi({ apiKey }: { apiKey: string }) {
6
+ const paramsSchema = z.object({
7
+ input: z.string(),
8
+ });
9
+ const name = 'serpApi';
10
+ const description = 'A custom search engine. Useful for when you need to answer questions about current events. Input should be a search query. Outputs a JSON array of results.';
11
+
12
+ const execute = async ({ input }: z.infer<typeof paramsSchema>) => {
13
+ try {
14
+ const response = await getJson({
15
+ engine: "google",
16
+ api_key: apiKey,
17
+ q: input,
18
+ location: "Seattle, Washington",
19
+ });
20
+
21
+ return JSON.stringify(response);
22
+ } catch (error) {
23
+ throw new Error(`Error in serpApi: ${error}`);
24
+ }
25
+ };
26
+
27
+ return new Tool(paramsSchema, name, description, execute).tool;
28
+ }
29
+
30
+ export { createSerpApi };
package-lock.json CHANGED
@@ -28,6 +28,7 @@
28
  "react": "^18",
29
  "react-dom": "^18",
30
  "react-markdown": "^9.0.0",
 
31
  "sonner": "^1.1.0",
32
  "zod": "^3.22.4",
33
  "zod-to-json-schema": "^3.21.4"
@@ -7245,6 +7246,11 @@
7245
  "node": ">=10"
7246
  }
7247
  },
 
 
 
 
 
7248
  "node_modules/set-function-length": {
7249
  "version": "1.1.1",
7250
  "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
 
28
  "react": "^18",
29
  "react-dom": "^18",
30
  "react-markdown": "^9.0.0",
31
+ "serpapi": "^2.0.0",
32
  "sonner": "^1.1.0",
33
  "zod": "^3.22.4",
34
  "zod-to-json-schema": "^3.21.4"
 
7246
  "node": ">=10"
7247
  }
7248
  },
7249
+ "node_modules/serpapi": {
7250
+ "version": "2.0.0",
7251
+ "resolved": "https://registry.npmjs.org/serpapi/-/serpapi-2.0.0.tgz",
7252
+ "integrity": "sha512-LKrIBrXWhZDmS1urFwB8e/T8omW2C7r7ZmW231aVBtMbfNx/p9pi5gkiScM/VSAcsyhX1q3oPeWCvbzQiiwaCg=="
7253
+ },
7254
  "node_modules/set-function-length": {
7255
  "version": "1.1.1",
7256
  "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
package.json CHANGED
@@ -29,6 +29,7 @@
29
  "react": "^18",
30
  "react-dom": "^18",
31
  "react-markdown": "^9.0.0",
 
32
  "sonner": "^1.1.0",
33
  "zod": "^3.22.4",
34
  "zod-to-json-schema": "^3.21.4"
 
29
  "react": "^18",
30
  "react-dom": "^18",
31
  "react-markdown": "^9.0.0",
32
+ "serpapi": "^2.0.0",
33
  "sonner": "^1.1.0",
34
  "zod": "^3.22.4",
35
  "zod-to-json-schema": "^3.21.4"
pages/api/{surfer β†’ functions}/index.ts RENAMED
@@ -7,6 +7,7 @@ import puppeteer from 'puppeteer';
7
  import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
8
  import { MemoryVectorStore } from 'langchain/vectorstores/memory';
9
  import { HuggingFaceTransformersEmbeddings } from "langchain/embeddings/hf_transformers";
 
10
 
11
  export const config = {
12
  api: {
@@ -31,6 +32,11 @@ const model = new HuggingFaceTransformersEmbeddings({
31
 
32
  const urlRegex = /(https?:\/\/[^\s]+)/g;
33
 
 
 
 
 
 
34
  const handleContentText = async (targetUrl: string) => {
35
  const response = await fetch(targetUrl);
36
  const contentType = response.headers.get('content-type') || '';
@@ -60,6 +66,7 @@ const handleContentText = async (targetUrl: string) => {
60
 
61
  export default async function handler(req: NextApiRequest, res: NextApiResponse) {
62
  const prompt = req.body.prompt as string;
 
63
  const urls = prompt.match(urlRegex);
64
  const targetUrl = urls ? urls[0] : null;
65
  const promptWithoutUrl = urls ? prompt.replace(urlRegex, '').trim() : prompt;
@@ -69,23 +76,28 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
69
  }
70
 
71
  try {
72
- const content: string = await handleContentText(targetUrl)
73
- if (!content) {
74
- return `Couldn't find ${targetUrl}, here is the prompt: ${promptWithoutUrl}`;
75
- }
76
-
77
- const documents = await textSplitter.createDocuments([content]);
 
 
 
 
78
 
79
- const vectorStore = await MemoryVectorStore.fromTexts(
80
- // @ts-ignore
81
- [...documents.map(doc => doc.pageContent)],
82
- // @ts-ignore
83
- [...documents.map((v, k) => k)],
84
- model
85
- )
86
- const queryResult = await vectorStore.similaritySearch(promptWithoutUrl, VECTOR_STORE_SIZE);
87
- return res.status(200).send(
88
- `Here is the context: ${JSON.stringify(queryResult.map(result => result.pageContent))} from using the prompt to lookup relevant information. Here is the prompt: ${promptWithoutUrl}`);
 
89
  } catch (error) {
90
  console.error(error);
91
  // @ts-ignore
 
7
  import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
8
  import { MemoryVectorStore } from 'langchain/vectorstores/memory';
9
  import { HuggingFaceTransformersEmbeddings } from "langchain/embeddings/hf_transformers";
10
+ import { createSerpApi } from '../../../app/tools/serp-api'
11
 
12
  export const config = {
13
  api: {
 
32
 
33
  const urlRegex = /(https?:\/\/[^\s]+)/g;
34
 
35
+ const [serpApi] =
36
+ createSerpApi({
37
+ apiKey: process.env.SERP_API_KEY || "",
38
+ });
39
+
40
  const handleContentText = async (targetUrl: string) => {
41
  const response = await fetch(targetUrl);
42
  const contentType = response.headers.get('content-type') || '';
 
66
 
67
  export default async function handler(req: NextApiRequest, res: NextApiResponse) {
68
  const prompt = req.body.prompt as string;
69
+ const functionName = req.body.name as string;
70
  const urls = prompt.match(urlRegex);
71
  const targetUrl = urls ? urls[0] : null;
72
  const promptWithoutUrl = urls ? prompt.replace(urlRegex, '').trim() : prompt;
 
76
  }
77
 
78
  try {
79
+ if (functionName === 'serpApi') {
80
+ const result = await serpApi({ input: prompt });
81
+ return res.status(200).send(result);
82
+ } else {
83
+ const content: string = await handleContentText(targetUrl)
84
+ if (!content) {
85
+ return `Couldn't find ${targetUrl}, here is the prompt: ${promptWithoutUrl}`;
86
+ }
87
+
88
+ const documents = await textSplitter.createDocuments([content]);
89
 
90
+ const vectorStore = await MemoryVectorStore.fromTexts(
91
+ // @ts-ignore
92
+ [...documents.map(doc => doc.pageContent)],
93
+ // @ts-ignore
94
+ [...documents.map((v, k) => k)],
95
+ model
96
+ )
97
+ const queryResult = await vectorStore.similaritySearch(promptWithoutUrl, VECTOR_STORE_SIZE);
98
+ return res.status(200).send(
99
+ `Here is the context: ${JSON.stringify(queryResult.map(result => result.pageContent))} from using the prompt to lookup relevant information. Here is the prompt: ${promptWithoutUrl}`);
100
+ }
101
  } catch (error) {
102
  console.error(error);
103
  // @ts-ignore