UNPKG

llamaindex

Version:

<p align="center"> <img height="100" width="100" alt="LlamaIndex logo" src="https://ts.llamaindex.ai/square.svg" /> </p> <h1 align="center">LlamaIndex.TS</h1> <h3 align="center"> Data framework for your LLM application. </h3>

312 lines (311 loc) 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { ReACTAgentWorker: function() { return ReACTAgentWorker; }, ReActAgent: function() { return ReActAgent; } }); const _agent = require("@llamaindex/core/agent"); const _utils = require("@llamaindex/core/utils"); const _env = require("@llamaindex/env"); const _react = require("../internal/prompt/react.js"); const _utils1 = require("../internal/utils.js"); const _Settings = require("../Settings.js"); function reasonFormatter(reason) { switch(reason.type){ case "observation": return `Observation: ${(0, _utils.stringifyJSONToMessageContent)(reason.observation)}`; case "action": return `Thought: ${reason.thought}\nAction: ${reason.action}\nInput: ${(0, _utils.stringifyJSONToMessageContent)(reason.input)}`; case "response": { return `Thought: ${reason.thought}\nAnswer: ${(0, _utils.extractText)(reason.response.message.content)}`; } } } function extractJsonStr(text) { const pattern = /\{.*\}/s; const match = text.match(pattern); if (!match) { throw new SyntaxError(`Could not extract json string from output: ${text}`); } return match[0]; } function extractFinalResponse(inputText) { const pattern = /\s*Thought:(.*?)Answer:(.*?)$/s; const match = inputText.match(pattern); if (!match) { throw new Error(`Could not extract final answer from input text: ${inputText}`); } const thought = match[1].trim(); const answer = match[2].trim(); return [ thought, answer ]; } function extractToolUse(inputText) { const pattern = /\s*Thought: (.*?)\nAction: ([a-zA-Z0-9_]+).*?\.*[Input:]*.*?(\{.*?\})/s; const match = inputText.match(pattern); if (!match) { throw new Error(`Could not extract tool use from input text: "${inputText}"`); } const thought = match[1].trim(); const action = match[2].trim(); const actionInput = match[3].trim(); return [ thought, action, actionInput ]; } function actionInputParser(jsonStr) { const processedString = jsonStr.replace(/(?<!\w)'|'(?!\w)/g, '"'); const pattern = /"(\w+)":\s*"([^"]*)"/g; const matches = [ ...processedString.matchAll(pattern) ]; return Object.fromEntries(matches); } const reACTOutputParser = async (output, onResolveType)=>{ let reason = null; if ((0, _utils1.isAsyncIterable)(output)) { const [peakStream, finalStream] = (0, _agent.createReadableStream)(output).tee(); const reader = peakStream.getReader(); let type = null; let content = ""; for(;;){ const { done, value } = await reader.read(); if (done) { break; } content += value.delta; if (content.includes("Action:")) { type = "action"; } else if (content.includes("Answer:")) { type = "answer"; } } if (type === null) { // `Thought:` is always present at the beginning of the output. type = "thought"; } reader.releaseLock(); if (!type) { throw new Error("Could not determine type of output"); } onResolveType(type, finalStream); // step 2: do the parsing from content switch(type){ case "action": { // have to consume the stream to get the full content const response = await (0, _agent.consumeAsyncIterable)(peakStream, content); const [thought, action, input] = extractToolUse(response.content); const jsonStr = extractJsonStr(input); let json; try { json = JSON.parse(jsonStr); } catch (e) { json = actionInputParser(jsonStr); } reason = { type: "action", thought, action, input: json }; break; } case "thought": { const thought = "(Implicit) I can answer without any more tools!"; const response = await (0, _agent.consumeAsyncIterable)(peakStream, content); reason = { type: "response", thought, response: { raw: peakStream, message: response } }; break; } case "answer": { const response = await (0, _agent.consumeAsyncIterable)(peakStream, content); const [thought, answer] = extractFinalResponse(response.content); reason = { type: "response", thought, response: { raw: response, message: { role: "assistant", content: answer } } }; break; } default: { throw new Error(`Invalid type: ${type}`); } } } else { const content = (0, _utils.extractText)(output.message.content); const type = content.includes("Answer:") ? "answer" : content.includes("Action:") ? "action" : "thought"; onResolveType(type, output); // step 2: do the parsing from content switch(type){ case "action": { const [thought, action, input] = extractToolUse(content); const jsonStr = extractJsonStr(input); let json; try { json = JSON.parse(jsonStr); } catch (e) { json = actionInputParser(jsonStr); } reason = { type: "action", thought, action, input: json }; break; } case "thought": { const thought = "(Implicit) I can answer without any more tools!"; reason = { type: "response", thought, response: { raw: output, message: { role: "assistant", content: (0, _utils.extractText)(output.message.content) } } }; break; } case "answer": { const [thought, answer] = extractFinalResponse(content); reason = { type: "response", thought, response: { raw: output, message: { role: "assistant", content: answer } } }; break; } default: { throw new Error(`Invalid type: ${type}`); } } } if (reason === null) { throw new TypeError("Reason is null"); } return reason; }; const chatFormatter = async (tools, messages, currentReasons)=>{ const header = (0, _react.getReACTAgentSystemHeader)(tools); const reasonMessages = []; for (const reason of currentReasons){ const response = await reasonFormatter(reason); reasonMessages.push({ role: reason.type === "observation" ? "user" : "assistant", content: response }); } return [ { role: "system", content: header }, ...messages, ...reasonMessages ]; }; class ReACTAgentWorker extends _agent.AgentWorker { taskHandler = ReActAgent.taskHandler; } class ReActAgent extends _agent.AgentRunner { constructor(params){ (0, _agent.validateAgentParams)(params); super({ llm: params.llm ?? _Settings.Settings.llm, chatHistory: params.chatHistory ?? [], runner: new ReACTAgentWorker(), systemPrompt: params.systemPrompt ?? null, tools: "tools" in params ? params.tools : params.toolRetriever.retrieve.bind(params.toolRetriever), verbose: params.verbose ?? false }); } createStore() { return { reasons: [] }; } static taskHandler = async (step, enqueueOutput)=>{ const { llm, stream, getTools } = step.context; const lastMessage = step.context.store.messages.at(-1).content; const tools = await getTools(lastMessage); const messages = await chatFormatter(tools, step.context.store.messages, step.context.store.reasons); const response = await llm.chat({ // @ts-expect-error boolean stream, messages }); const reason = await reACTOutputParser(response, (type, response)=>{ enqueueOutput({ taskStep: step, output: response, isLast: type !== "action" }); }); step.context.logger.log("current reason: %O", reason); step.context.store.reasons = [ ...step.context.store.reasons, reason ]; if (reason.type === "action") { const tool = tools.find((tool)=>tool.metadata.name === reason.action); const toolOutput = await (0, _agent.callTool)(tool, { id: (0, _env.randomUUID)(), input: reason.input, name: reason.action }, step.context.logger); step.context.store.reasons = [ ...step.context.store.reasons, { type: "observation", observation: toolOutput.output } ]; } }; }