ホームページ  >  記事  >  ウェブフロントエンド  >  NodeJS を使用して ReAct Agent を最初から作成する (wikipedia 検索)

NodeJS を使用して ReAct Agent を最初から作成する (wikipedia 検索)

DDD
DDDオリジナル
2024-09-24 10:30:10877ブラウズ

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

導入

Wikipedia を検索し、見つかった情報に基づいて質問に答えることができる AI エージェントを作成します。この ReAct (Reason and Act) エージェントは、Google Generative AI API を使用してクエリを処理し、応答を生成します。私たちのエージェントは次のことができるようになります:

  1. 関連情報については Wikipedia を検索してください。
  2. Wikipedia ページから特定のセクションを抽出します。
  3. 収集した情報について理由を考え、答えを組み立てます。

[2] ReAct Agentとは何ですか?

ReAct Agent は、Reflection-Action サイクルに従う特定のタイプのエージェントです。利用可能な情報と実行可能なアクションに基づいて現在のタスクを反映し、どのアクションを実行するか、またはタスクを完了するかどうかを決定します。

[3] エージェントの計画

3.1 必要なツール

  • Node.js
  • HTTP リクエスト用の Axios ライブラリ
  • Google Generative AI API (gemini-1.5-flash)
  • ウィキペディア API

3.2 エージェントの構造

ReAct Agent には 3 つの主な状態があります:

  1. 考えたこと(反省)
  2. アクション (実行)
  3. ANSWER(応答)

[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] Wikipedia 部分の仕組み

Wikipedia との対話は 2 つの主な手順で行われます:

  1. 初期検索 (wikipedia 機能):

    • Wikipedia 検索 API にリクエストを送信します。
    • クエリに対して最大 4 つの関連結果を返します。
    • 結果ごとに、ページのセクションを取得します。
  2. 詳細検索 (wikipedia_with_pageId 関数):

    • ページ ID とセクション ID を使用して、特定のコンテンツを取得します。
    • 要求されたセクションのテキストを返します。

このプロセスにより、エージェントはまずクエリに関連するトピックの概要を把握し、次に必要に応じて特定のセクションをさらに深く掘り下げることができます。

[6] 実行フロー例

  1. ユーザーが質問します。
  2. エージェントは THOUGHT 状態に入り、質問について考えます。
  3. Wikipedia を検索することを決定し、ACTION 状態に入ります。
  4. wikipedia 関数を実行し、結果を取得します。
  5. THOUGHT 状態に戻り、結果を反映します。
  6. さらに詳細な情報や別のアプローチを検索することを決定する場合があります。
  7. 必要に応じて思考と行動のサイクルを繰り返します。
  8. 十分な情報がある場合、ANSWER 状態に入ります。
  9. 収集されたすべての情報に基づいて最終的な回答を生成します。
  10. ウィキペディアに収集するデータがない場合は常に無限ループに入ります。タイマーで修正します =P

[7] 最終的な考慮事項

  • モジュール構造により、新しいツールや API を簡単に追加できます。
  • 無限ループや過剰なリソースの使用を避けるために、エラー処理と時間/反復制限を実装することが重要です。
  • 使用温度: 99999 (笑)

以上がNodeJS を使用して ReAct Agent を最初から作成する (wikipedia 検索)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。