字数:2.4k 字
预计:14 分钟
阅读量:
Demo(向量搜索)
作者:winches
更新于:10 个月前
ts
import { CharacterTextSplitter } from 'langchain/text_splitter'
import { TextLoader } from 'langchain/document_loaders/fs/text'
import { MemoryVectorStore } from 'langchain/vectorstores/memory'
import { HuggingFaceTransformersEmbeddings } from 'langchain/embeddings/hf_transformers'
import { env } from '@xenova/transformers'
import { DirectoryLoader } from 'langchain/document_loaders/fs/directory'
env.localModelPath = 'src/models';
(async function () {
const loader = new DirectoryLoader('E:/myself/new-project--------------------t/langchain/text', {
'.txt': path => new TextLoader(path),
})
const docs = await loader.load()
const textSplitter = new CharacterTextSplitter({ chunkSize: 10, chunkOverlap: 0, separator: '\r\n' })
const splitDocs = await textSplitter.splitDocuments(docs)
const embeddings = new HuggingFaceTransformersEmbeddings({
modelName: 'shibing624/text2vec-base-chinese',
})
const docSearch = await MemoryVectorStore.fromDocuments(splitDocs, embeddings)
const resultOne = await docSearch.similaritySearch('你好', 1)
console.log(resultOne)
}())格式化输出
ts
const parser = StructuredOutputParser.fromZodSchema(
z.object({
cmd: z.string().describe('the command need to run'),
description: z.string().describe('explain the purpose of this cmd'),
execute: z.boolean().default(true),
}),
)
parser.getFormatInstructions()输出:
info
You must format your output as a JSON value that adheres to a given "JSON Schema" instance.
"JSON Schema" is a declarative language that allows you to annotate and validate JSON documents.
For example, the example "JSON Schema" instance {{"properties": {{"foo": {{"description": "a list of test words", "type": "array", "items": {{"type": "string"}}}}}}, "required": ["foo"]}}}}
would match an object with one required property, "foo". The "type" property specifies "foo" must be an "array", and the "description" property semantically describes it as "a list of test words". The items within "foo" must be strings.
Thus, the object {{"foo": ["bar", "baz"]}} is a well-formatted instance of this example "JSON Schema". The object {{"properties": {{"foo": ["bar", "baz"]}}}} is not well-formatted.
Your output will be parsed and type-checked according to the provided schema instance, so make sure all fields in your output match the schema exactly and there are no trailing commas!
Here is the JSON Schema instance your output must adhere to. Include the enclosing markdown codeblock:
{"type":"object","properties":{"cmd":{"type":"string","description":"the command need to run"},"description":{"type":"string","description":"explain the purpose of this cmd"},"execute":{"type":"boolean","default":true}},"required":["cmd","description"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}promptTemplate
ts
new PromptTemplate({
template: 'I want you to act as a terminal, analyze the question I pose, and format your response as a JSON object.\n\nMy question:\n{question}.\n\nOutput format:\n{parse}',
inputVariables: ['question', 'parse'],
}).format({
parse: parser.getFormatInstructions(),
question: '查看全部分支',
})输出:
I want you to act as a terminal, analyze the question I pose, and format your response as a JSON object.
My question:查看全部分支.
Output format: 上面的格式化输出
AI app
Tools
What about extending the language model's capabilities beyond just generating text?
ts
import { openai } from '@ai-sdk/openai'
import { generateText, tool } from 'ai'
import 'dotenv/config'
import { z } from 'zod'
async function main() {
const result = await generateText({
model: openai('gpt-4o'),
prompt: 'What\'s 10 + 5?',
tools: {
addNumbers: tool({
description: 'Add two numbers together',
parameters: z.object({
num1: z.number(),
num2: z.number(),
}),
execute: async ({ num1, num2 }) => {
return num1 + num2
},
}),
},
})
console.log(result.toolResults)
}
main()With tools, you can allow the model to execute any arbitrary code, such as fetching data from an API or interacting with a database.
- But we've had to log out the tool results and the model hasn't actually answered the question. How can we get the model to answer our question?
ts
import { openai } from '@ai-sdk/openai'
import { generateText, tool } from 'ai'
import 'dotenv/config'
import { z } from 'zod'
async function main() {
const result = await generateText({
model: openai('gpt-4o'),
prompt: 'What\'s 10 + 5?',
maxSteps: 2,
tools: {
addNumbers: tool({
description: 'Add two numbers together',
parameters: z.object({
num1: z.number(),
num2: z.number(),
}),
execute: async ({ num1, num2 }) => {
return num1 + num2
},
}),
},
})
console.log(result.steps.length)
console.log(result.text)
}
main()- But what about multiple tools over multiple steps?
ts
import { openai } from '@ai-sdk/openai'
import { generateText, tool } from 'ai'
import 'dotenv/config'
import { z } from 'zod'
async function main() {
const result = await generateText({
model: openai('gpt-4o'),
prompt: 'Get the weather in SF and NY, then add them together.',
maxSteps: 3,
tools: {
addNumbers: tool({
description: 'Add two numbers together',
parameters: z.object({
num1: z.number(),
num2: z.number(),
}),
execute: async ({ num1, num2 }) => {
return num1 + num2
},
}),
getWeather: tool({
description: 'Get the current weather at a location',
parameters: z.object({
latitude: z.number(),
longitude: z.number(),
city: z.string(),
}),
execute: async ({ latitude, longitude, city }) => {
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,weathercode,relativehumidity_2m&timezone=auto`,
)
const weatherData = await response.json()
return {
temperature: weatherData.current.temperature_2m,
weatherCode: weatherData.current.weathercode,
humidity: weatherData.current.relativehumidity_2m,
city,
}
},
}),
},
})
console.log(result.steps.length)
console.log(result.text)
}
main()Deep search
Rough steps will be
- Take input
- Generate search queries
- Map through each query and
- Search the web for a relevant result
- Analyze the result for learnings and follow-up questions
- If depth > 0, follow-up with a new query
e.g. Let's say you're researching "Electric Cars" with depth = 2 and breadth = 3
text
Level 0 (Initial Query): "Electric Cars"
│
├── Level 1 (depth = 1):
│ ├── Sub-query 1: "Tesla Model 3 specifications"
│ ├── Sub-query 2: "Electric car charging infrastructure"
│ └── Sub-query 3: "Electric vehicle battery technology"
│
└── Level 2 (depth = 2):
├── From Sub-query 1:
│ ├── "Model 3 range capacity"
│ └── "Model 3 pricing"
│
├── From Sub-query 2:
│ ├── "Fast charging stations in US"
│ └── "Home charging installation"
│
└── From Sub-query 3:
├── "Lithium ion battery lifespan"
└── "Solid state batteries"Let's get started!
- Start by creating a function to generate search queries.
ts
import { openai } from '@ai-sdk/openai'
import { generateObject } from 'ai'
import { z } from 'zod'
import 'dotenv/config'
const mainModel = openai('gpt-4o')
async function generateSearchQueries(query: string, n = 3) {
const {
object: { queries },
} = await generateObject({
model: mainModel,
prompt: `Generate ${n} search queries for the following query: ${query}`,
schema: z.object({
queries: z.array(z.string()).min(1).max(5),
}),
})
return queries
}- Add a main function to run this function.
ts
async function main() {
const prompt = 'What do you need to be a D1 shotput athlete?'
const queries = await generateSearchQueries(prompt)
}
main()- Add a function to search the web.
ts
import Exa from 'exa-js'
const exa = new Exa(process.env.EXA_API_KEY)
interface SearchResult {
title: string
url: string
content: string
}
async function searchWeb(query: string) {
const { results } = await exa.searchAndContents(query, {
numResults: 1,
livecrawl: 'always',
})
return results.map(
r =>
({
title: r.title,
url: r.url,
content: r.text,
}) as SearchResult
)
}- Create a function to orchestrate search.
ts
async function searchAndProcess(query: string) {
const pendingSearchResults: SearchResult[] = []
const finalSearchResults: SearchResult[] = []
await generateText({
model: mainModel,
prompt: `Search the web for information about ${query}`,
system:
'You are a researcher. For each query, search the web and then evaluate if the results are relevant and will help answer the following query',
maxSteps: 5,
tools: {
searchWeb: tool({
description: 'Search the web for information about a given query',
parameters: z.object({
query: z.string().min(1),
}),
async execute({ query }) {
const results = await searchWeb(query)
pendingSearchResults.push(...results)
return results
},
}),
evaluate: tool({
description: 'Evaluate the search results',
parameters: z.object({}),
async execute() {
const pendingResult = pendingSearchResults.pop()!
const { object: evaluation } = await generateObject({
model: mainModel,
prompt: `Evaluate whether the search results are relevant and will help answer the following query: ${query}. If the page already exists in the existing results, mark it as irrelevant.
<search_results>
${JSON.stringify(pendingResult)}
</search_results>
`,
output: 'enum',
enum: ['relevant', 'irrelevant'],
})
if (evaluation === 'relevant')
finalSearchResults.push(pendingResult)
console.log('Found:', pendingResult.url)
console.log('Evaluation completed:', evaluation)
return evaluation === 'irrelevant'
? 'Search results are irrelevant. Please search again with a more specific query.'
: 'Search results are relevant. End research for this query.'
},
}),
},
})
return finalSearchResults
}Don't forget to add the system prompt!
- Update the main function:
ts
async function main() {
const prompt = 'What do you need to be a D1 shotput athlete?'
const queries = await generateSearchQueries(prompt)
for (const query of queries) {
console.log(`Searching the web for: ${query}`)
const searchResults = await searchAndProcess(query)
}
}
main()- Create the function to generate learnings:
ts
async function generateLearnings(query: string, searchResult: SearchResult) {
const { object } = await generateObject({
model: mainModel,
prompt: `The user is researching "${query}". The following search result were deemed relevant.
Generate a learning and a follow-up question from the following search result:
<search_result>
${JSON.stringify(searchResult)}
</search_result>
`,
schema: z.object({
learning: z.string(),
followUpQuestions: z.array(z.string()),
}),
})
return object
}- Update the main function:
ts
async function main() {
const prompt = 'What do you need to be a D1 shotput athlete?'
const queries = await generateSearchQueries(prompt)
for (const query of queries) {
console.log(`Searching the web for: ${query}`)
const searchResults = await searchAndProcess(query)
for (const searchResult of searchResults) {
console.log(`Processing search result: ${searchResult.url}`)
const learnings = await generateLearnings(query, searchResult)
}
}
}
main()- Refactor main function to allow for recursion:
ts
async function deepResearch(query: string,
depth = 1,
breadth = 3) {
const queries = await generateSearchQueries(query)
for (const query of queries) {
console.log(`Searching the web for: ${query}`)
const searchResults = await searchAndProcess(query)
for (const searchResult of searchResults) {
console.log(`Processing search result: ${searchResult.url}`)
const learnings = await generateLearnings(query, searchResult)
// call deepResearch recursively with decrementing depth and breadth
}
}
}
async function main() {
const prompt = 'What do you need to be a D1 shotput athlete?'
const research = await deepResearch(prompt)
}
main()- Create an accumulated research object to store all learnings and follow-up questions.
ts
interface Learning {
learning: string
followUpQuestions: string[]
}
interface Research {
query: string | undefined
queries: string[]
searchResults: SearchResult[]
learnings: Learning[]
completedQueries: string[]
}
const accumulatedResearch: Research = {
query: undefined,
queries: [],
searchResults: [],
learnings: [],
completedQueries: [],
}- Update accumulatedResearch throughout process
ts
async function deepResearch(prompt: string,
depth = 2,
breadth = 2) {
if (!accumulatedResearch.query)
accumulatedResearch.query = prompt
const queries = await generateSearchQueries(prompt, breadth)
accumulatedResearch.queries = queries
for (const query of queries) {
console.log(`Searching the web for: ${query}`)
const searchResults = await searchAndProcess(query)
accumulatedResearch.searchResults.push(...searchResults)
for (const searchResult of searchResults) {
console.log(`Processing search result: ${searchResult.url}`)
const learnings = await generateLearnings(query, searchResult)
accumulatedResearch.learnings.push(learnings)
accumulatedResearch.completedQueries.push(query)
// call deepResearch recursively with decrementing depth and breadth
}
}
}- Add recursion.
ts
async function deepResearch(prompt: string,
depth = 2,
breadth = 2) {
if (!accumulatedResearch.query)
accumulatedResearch.query = prompt
if (depth === 0)
return accumulatedResearch
const queries = await generateSearchQueries(prompt, breadth)
accumulatedResearch.queries = queries
for (const query of queries) {
console.log(`Searching the web for: ${query}`)
const searchResults = await searchAndProcess(query)
accumulatedResearch.searchResults.push(...searchResults)
for (const searchResult of searchResults) {
console.log(`Processing search result: ${searchResult.url}`)
const learnings = await generateLearnings(query, searchResult)
accumulatedResearch.learnings.push(learnings)
accumulatedResearch.completedQueries.push(query)
const newQuery = `Overall research goal: ${prompt}
Previous search queries: ${accumulatedResearch.completedQueries.join(', ')}
Follow-up questions: ${learnings.followUpQuestions.join(', ')}
`
await deepResearch(newQuery, depth - 1, Math.ceil(breadth / 2))
}
}
return accumulatedResearch
}- Update searchAndProcess to take in previous queries.
ts
async function searchAndProcess(query: string,
accumulatedSources: SearchResult[]) {
const pendingSearchResults: SearchResult[] = []
const finalSearchResults: SearchResult[] = []
await generateText({
model: mainModel,
prompt: `Search the web for information about ${query}`,
system:
'You are a researcher. For each query, search the web and then evaluate if the results are relevant and will help answer the following query',
maxSteps: 5,
tools: {
searchWeb: tool({
description: 'Search the web for information about a given query',
parameters: z.object({
query: z.string().min(1),
}),
async execute({ query }) {
const results = await searchWeb(query)
pendingSearchResults.push(...results)
return results
},
}),
evaluate: tool({
description: 'Evaluate the search results',
parameters: z.object({}),
async execute() {
const pendingResult = pendingSearchResults.pop()!
const { object: evaluation } = await generateObject({
model: mainModel,
prompt: `Evaluate whether the search results are relevant and will help answer the following query: ${query}. If the page already exists in the existing results, mark it as irrelevant.
<search_results>
${JSON.stringify(pendingResult)}
</search_results>
<existing_results>
${JSON.stringify(accumulatedSources.map(result => result.url))}
</existing_results>
`,
output: 'enum',
enum: ['relevant', 'irrelevant'],
})
if (evaluation === 'relevant')
finalSearchResults.push(pendingResult)
console.log('Found:', pendingResult.url)
console.log('Evaluation completed:', evaluation)
return evaluation === 'irrelevant'
? 'Search results are irrelevant. Please search again with a more specific query.'
: 'Search results are relevant. End research for this query.'
},
}),
},
})
return finalSearchResults
}- Update deepResearch function
ts
async function deepResearch(prompt: string,
depth = 2,
breadth = 2) {
if (!accumulatedResearch.query)
accumulatedResearch.query = prompt
if (depth === 0)
return accumulatedResearch
const queries = await generateSearchQueries(prompt, breadth)
accumulatedResearch.queries = queries
for (const query of queries) {
console.log(`Searching the web for: ${query}`)
const searchResults = await searchAndProcess(
query,
accumulatedResearch.searchResults
)
accumulatedResearch.searchResults.push(...searchResults)
for (const searchResult of searchResults) {
console.log(`Processing search result: ${searchResult.url}`)
const learnings = await generateLearnings(query, searchResult)
accumulatedResearch.learnings.push(learnings)
accumulatedResearch.completedQueries.push(query)
const newQuery = `Overall research goal: ${prompt}
Previous search queries: ${accumulatedResearch.completedQueries.join(', ')}
Follow-up questions: ${learnings.followUpQuestions.join(', ')}
`
await deepResearch(newQuery, depth - 1, Math.ceil(breadth / 2))
}
}
return accumulatedResearch
}- Create generateReport function
ts
async function generateReport(research: Research) {
const { text } = await generateText({
model: openai('o3-mini'),
prompt:
`Generate a report based on the following research data:\n\n${
JSON.stringify(research, null, 2)}`,
})
return text
}- Update main function
ts
async function main() {
const research = await deepResearch(
'What do you need to be a D1 shotput athlete?'
)
console.log('Research completed!')
console.log('Generating report...')
const report = await generateReport(research)
console.log('Report generated! report.md')
fs.writeFileSync('report.md', report)
}
main()- Update system prompt:
ts
const SYSTEM_PROMPT = `You are an expert researcher. Today is ${new Date().toISOString()}. Follow these instructions when responding:
- You may be asked to research subjects that is after your knowledge cutoff, assume the user is right when presented with news.
- The user is a highly experienced analyst, no need to simplify it, be as detailed as possible and make sure your response is correct.
- Be highly organized.
- Suggest solutions that I didn't think about.
- Be proactive and anticipate my needs.
- Treat me as an expert in all subject matter.
- Mistakes erode my trust, so be accurate and thorough.
- Provide detailed explanations, I'm comfortable with lots of detail.
- Value good arguments over authorities, the source is irrelevant.
- Consider new technologies and contrarian ideas, not just the conventional wisdom.
- You may use high levels of speculation or prediction, just flag it for me.
- Use Markdown formatting.`
async function generateReport(research: Research) {
const { text } = await generateText({
model: openai('o3-mini'),
system: SYSTEM_PROMPT,
prompt:
`Generate a report based on the following research data:\n\n${
JSON.stringify(research, null, 2)}`,
})
return text
}
Awesome 15docs