首頁 >web前端 >js教程 >使用 nodeJS 從頭開始建立 ReAct Agent(維基百科搜尋)

使用 nodeJS 從頭開始建立 ReAct Agent(維基百科搜尋)

DDD
DDD原創
2024-09-24 10:30:10907瀏覽

Creating a ReAct Agent from the scratch with nodeJS ( wikipedia search )

介紹

我們將創建一個能夠搜尋維基百科並根據找到的資訊回答問題的人工智慧代理。該 ReAct(理性與行動)代理程式使用 Google Generative AI API 來處理查詢並產生回應。我們的代理商將能夠:

  1. 搜尋維基百科取得相關資訊。
  2. 從維基百科頁面擷取特定部分。
  3. 對收集到的資訊進行推理並制定答案。

[2] 什麼是ReAct代理?

ReAct Agent 是一種遵循反射-操作循環的特定類型的代理。它根據可用資訊和它可以執行的操作反映當前任務,然後決定採取哪個操作或是否結束任務。

[3] 規劃代理

3.1 所需工具

  • Node.js
  • 用於 HTTP 請求的 Axios 庫
  • Google 生成式 AI API (gemini-1.5-flash)
  • 維基百科 API

3.2 代理結構

我們的 ReAct Agent 將有三個主要狀態:

  1. 思想(反思)
  2. 行動(執行)
  3. 答案(回覆)

[4] 實作代理

讓我們逐步建立 ReAct Agent,突出顯示每個狀態。

4.1 初始設定

首先,設定專案並安裝依賴項:

mkdir react-agent-project
cd react-agent-project
npm init -y
npm install axios dotenv @google/generative-ai

在專案根目錄建立一個 .env 檔案:

GOOGLE_AI_API_KEY=your_api_key_here

4.2 建立Tools.js文件

使用以下內容建立 Tools.js:

const axios = require("axios");

class Tools {
  static async wikipedia(q) {
    try {
      const response = await axios.get("https://en.wikipedia.org/w/api.php", {
        params: {
          action: "query",
          list: "search",
          srsearch: q,
          srwhat: "text",
          format: "json",
          srlimit: 4,
        },
      });

      const results = await Promise.all(
        response.data.query.search.map(async (searchResult) => {
          const sectionResponse = await axios.get(
            "https://en.wikipedia.org/w/api.php",
            {
              params: {
                action: "parse",
                pageid: searchResult.pageid,
                prop: "sections",
                format: "json",
              },
            },
          );

          const sections = Object.values(
            sectionResponse.data.parse.sections,
          ).map((section) => `${section.index}, ${section.line}`);

          return {
            pageTitle: searchResult.title,
            snippet: searchResult.snippet,
            pageId: searchResult.pageid,
            sections: sections,
          };
        }),
      );

      return results
        .map(
          (result) =>
            `Snippet: ${result.snippet}\nPageId: ${result.pageId}\nSections: ${JSON.stringify(result.sections)}`,
        )
        .join("\n\n");
    } catch (error) {
      console.error("Error fetching from Wikipedia:", error);
      return "Error fetching data from Wikipedia";
    }
  }

  static async wikipedia_with_pageId(pageId, sectionId) {
    if (sectionId) {
      const response = await axios.get("https://en.wikipedia.org/w/api.php", {
        params: {
          action: "parse",
          format: "json",
          pageid: parseInt(pageId),
          prop: "wikitext",
          section: parseInt(sectionId),
          disabletoc: 1,
        },
      });
      return Object.values(response.data.parse?.wikitext ?? {})[0]?.substring(
        0,
        25000,
      );
    } else {
      const response = await axios.get("https://en.wikipedia.org/w/api.php", {
        params: {
          action: "query",
          pageids: parseInt(pageId),
          prop: "extracts",
          exintro: true,
          explaintext: true,
          format: "json",
        },
      });
      return Object.values(response.data?.query.pages)[0]?.extract;
    }
  }
}

module.exports = Tools;

4.3 建立ReactAgent.js文件

使用以下內容建立 ReactAgent.js:

require("dotenv").config();
const { GoogleGenerativeAI } = require("@google/generative-ai");
const Tools = require("./Tools");

const genAI = new GoogleGenerativeAI(process.env.GOOGLE_AI_API_KEY);

class ReActAgent {
  constructor(query, functions) {
    this.query = query;
    this.functions = new Set(functions);
    this.state = "THOUGHT";
    this._history = [];
    this.model = genAI.getGenerativeModel({
      model: "gemini-1.5-flash",
      temperature: 2,
    });
  }

  get history() {
    return this._history;
  }

  pushHistory(value) {
    this._history.push(`\n ${value}`);
  }

  async run() {
    this.pushHistory(`**Task: ${this.query} **`);
    try {
      return await this.step();
    } catch (e) {
      if (e.message.includes("exhausted")) {
        return "Sorry, I'm exhausted, I can't process your request anymore. ><";
      }
      return "Unable to process your request, please try again? ><";
    }
  }

  async step() {
    const colors = {
      reset: "\x1b[0m",
      yellow: "\x1b[33m",
      red: "\x1b[31m",
      cyan: "\x1b[36m",
    };

    console.log("====================================");
    console.log(
      `Next Movement: ${
        this.state === "THOUGHT"
          ? colors.yellow
          : this.state === "ACTION"
            ? colors.red
            : this.state === "ANSWER"
              ? colors.cyan
              : colors.reset
      }${this.state}${colors.reset}`,
    );
    console.log(`Last Movement: ${this.history[this.history.length - 1]}`);
    console.log("====================================");
    switch (this.state) {
      case "THOUGHT":
        await this.thought();
        break;
      case "ACTION":
        await this.action();
        break;
      case "ANSWER":
        await this.answer();
        break;
    }
  }

  async promptModel(prompt) {
    const result = await this.model.generateContent(prompt);
    const response = await result.response;
    return response.text();
  }

  async thought() {
    const availableFunctions = JSON.stringify(Array.from(this.functions));
    const historyContext = this.history.join("\n");
    const prompt = `Your task to FullFill ${this.query}.
Context contains all the reflection you made so far and the ActionResult you collected.
AvailableActions are functions you can call whenever you need more data.

Context: "${historyContext}" <<

AvailableActions: "${availableFunctions}" <<

Task: "${this.query}" <<

Reflect uppon Your Task using Context, ActionResult and AvailableActions to find your next_step.
print your next_step with a Thought or FullFill Your Task `;

    const thought = await this.promptModel(prompt);
    this.pushHistory(`\n **${thought.trim()}**`);
    if (
      thought.toLowerCase().includes("fullfill") ||
      thought.toLowerCase().includes("fulfill")
    ) {
      this.state = "ANSWER";
      return await this.step();
    }
    this.state = "ACTION";
    return await this.step();
  }

  async action() {
    const action = await this.decideAction();
    this.pushHistory(`** Action: ${action} **`);
    const result = await this.executeFunctionCall(action);
    this.pushHistory(`** ActionResult: ${result} **`);
    this.state = "THOUGHT";
    return await this.step();
  }

  async decideAction() {
    const availableFunctions = JSON.stringify(Array.from(this.functions));
    const historyContext = this.history;
    const prompt = `Reflect uppon the Thought, Query and AvailableActions

    ${historyContext[historyContext.length - 2]}

    Thought <<< ${historyContext[historyContext.length - 1]}

    Query: "${this.query}"

    AvailableActions: ${availableFunctions}

    output only the function,parametervalues separated by a comma. For example: "wikipedia,ronaldinho gaucho, 1450"`;

    const decision = await this.promptModel(prompt);
    return `${decision.replace(/`/g, "").trim()}`;
  }

  async executeFunctionCall(functionCall) {
    const [functionName, ...args] = functionCall.split(",");
    const func = Tools[functionName.trim()];
    if (func) {
      return await func.call(null, ...args);
    }
    throw new Error(`Function ${functionName} not found`);
  }

  async answer() {
    const historyContext = this.history;
    const prompt = `Based on the following context, provide a complete, detailed and descriptive formated answer for the Following Task: ${this.query} .

Context:
${historyContext}

Task: "${this.query}"`;

    const finalAnswer = await this.promptModel(prompt);
    this.history.push(`Answer: ${this.finalAnswer}`);
    console.log("WE WILL ANSWER >>>>>>>", finalAnswer);
    return finalAnswer;
  }
}

module.exports = ReActAgent;

4.4 運行代理程式(index.js)

使用以下內容建立index.js:

const ReActAgent = require("./ReactAgent.js");

async function main() {
  const query = "What does England border with?";
  const functions = [
    [
      "wikipedia",
      "params: query",
      "Semantic Search Wikipedia API for snippets, pageIds and sectionIds >> \n ex: Date brazil has been colonized? \n Brazil was colonized at 1500, pageId, sections : []",
    ],
    [
      "wikipedia_with_pageId",
      "params : pageId, sectionId",
      "Search Wikipedia API for data using a pageId and a sectionIndex as params.  \n ex: 1500, 1234 \n Section information about blablalbal",
    ],
  ];

  const agent = new ReActAgent(query, functions);
  try {
    const result = await agent.run();
    console.log("THE AGENT RETURN THE FOLLOWING >>>", result);
  } catch (e) {
    console.log("FAILED TO RUN T.T", e);
  }
}

main().catch(console.error);

[5] 維基百科部分如何運作

與維基百科的互動主要分為兩個步驟:

  1. 初始搜尋(維基百科功能):

    • 向維基百科搜尋 API 發出請求。
    • 最多回傳 4 個相關的查詢結果。
    • 對於每個結果,它都會取得頁面的各個部分。
  2. 詳細搜尋(wikipedia_with_pageId函數):

    • 使用頁面 ID 和部分 ID 來取得特定內容。
    • 傳回請求部分的文字。

此流程允許代理人首先獲得與查詢相關的主題的概述,然後根據需要深入研究特定部分。

[6] 執行流程範例

  1. 使用者提出問題。
  2. 智能體進入思考狀態並反思問題。
  3. 它決定搜尋維基百科並進入 ACTION 狀態。
  4. 執行wikipedia函數並取得結果。
  5. 返回THOUGHT狀態反思結果。
  6. 可能決定搜尋更多詳細資訊或不同的方法。
  7. 根據需要重複思想和行動循環。
  8. 當它有足夠的資訊時,它進入ANSWER狀態。
  9. 根據收集到的所有資訊產生最終答案。
  10. 只要維基百科沒有可收集的數據,就會進入無限循環。用計時器修復它=P

[7] 最後的考慮

  • 模組化結構可以輕鬆新增工具或 API。
  • 實作錯誤處理和時間/迭代限制非常重要,以避免無限循環或過度資源使用。
  • 使用溫度:99999 哈哈

以上是使用 nodeJS 從頭開始建立 ReAct Agent(維基百科搜尋)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn