t_wの輪郭

Feedlyでフォローするボタン
クローラー
import fs from "fs";
import * as R from "ramda";
import { Window } from "happy-dom";
import { urls } from "./urls";

const window = new Window();
const dom_parser = new window.DOMParser();

const queue = new Map<string, { got: boolean }>(
  urls.map((url) => [url, { got: false }])
);

// filesのファイル一覧
const file_names = fs.readdirSync("./files");
const file_names_base64 = file_names.map((file_name) => {
  const [url_base64, file_ext] = file_name.split(".");
  return url_base64;
});
const got_url_base64_set = new Set<string>(file_names_base64);
console.info({ got_url_base64_set });

for (;;) {
  try {
    const urls_for_scraping_groupby = R.groupBy((url) =>
      url.includes("pdf") ? "pdf" : "else"
    )([...queue].filter(([url, { got }]) => !got).map(([url, { got }]) => url));
    const urls_for_scraping = [
      ...(urls_for_scraping_groupby.pdf ?? []),
      ...(urls_for_scraping_groupby.else ?? []),
    ];

    if (urls_for_scraping.length === 0) {
      break;
    }

    const url = urls_for_scraping[0];
    console.info({ url });

    const url_base64 = Buffer.from(url).toString("base64");
    if (got_url_base64_set.has(url_base64)) {
      queue.set(url, { got: true });
      continue;
    }

    queue.set(url, { got: true });

    await sleep(2000);
    const fetch_result = await fetch(url);
    const file_type = fetch_result.headers.get("content-type");

    if (file_type === null) {
      console.warn("file type is null");
      continue;
    }

    // 拡張子
    const file_ext = file_type.split("/")[1];

    // urlをbase64にしてファイル名にする
    const file_name = `./files/${url_base64}.${file_ext}`;

    if (file_ext == "html") {
      const file_text = await fetch_result.text();
      const urls_next = await links_get_from_html_text(file_text);

      // Mapに追加
      urls_next
        .filter((url) => !queue.has(url) && url.includes("go.jp"))
        .forEach((url) => {
          queue.set(url, { got: false });
        });

      if (!file_text) {
        console.warn("file is null");
        continue;
      }

      await fs.writeFile(file_name, file_text, () => {});
    }

    if (file_ext == "pdf") {
      const file_name = `./files/${crypto.randomUUID()}.${file_ext}`;
      const array_buffer = await fetch_result.arrayBuffer();
      await fs.writeFile(file_name, Buffer.from(array_buffer), () => {});
    }
  } catch (error) {
    console.error("catch", error);
  }
}

async function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function links_get_from_html_text(text: string) {
  const dom = dom_parser.parseFromString(text, "text/html");
  const a_elems = dom.querySelectorAll(`a`);
  const urls = a_elems
    .map((a: any) => a.href)
    .filter((href) => href?.includes("go.jp"));
  return urls;
}
[Failed]大規模言語モデルで自分専用の税理士を作ってみる

この試みはめんどくさくなって途中で放棄された。
この文書に基づいて行動した結果、何らかの損害が生じたとしても、筆者は責任を取らない。
この文書に基づいて行動する前に「Librahack」について調べることを強く推奨する。


この文書について

この文書の目的

作業ログ

この文書の想定読者

JavaScriptが使えるITエンジニア
いつかやる気が出たときの私


方針

  1. go.jpドメインに限定して確定申告についてGoogle検索し、確定申告関連の情報が載ったファイルを取得する
  2. OpenAIのAssistantsに確定申告の情報が載ったファイルを食わせる
  3. Assistantsのプロンプトをチューニングする

go.jpドメインに限定してGoogle検索し、確定申告関連の情報が載ったファイルを取得する

  1. 「確定申告 site:go.jp」でGoogle検索する
  2. 検索結果からURLを抽出する
  3. 検索結果のURLからファイルをダウンロードする

「確定申告 site:go.jp」でGoogle検索する

Do it!!

検索結果からURLを抽出する

下記スクリプトをGoogle検索したページで開発者コンソールを開いて実行

$$(`a`)
  .map(a=>a.href)
  .filter(href=>href)
  .filter(href=>!/google/.test(href))

結果→Google検索で得られた、確定申告について情報が載っているサイトのURLの配列


検索結果のURLからファイルをダウンロードする

go.jpをクロールするスクリプト


OpenAIのAssistantsに確定申告の情報が載ったファイルを食わせる

400個ぐらいのファイルをアップロードしようとしたらエラーが出た。←いまここ


Note

OpenAIのウェブUIからAssistantsに大量にファイルをアップロードできないので、頑張ってRAGを実装する必要がある。ベクトルDBを用意し、PDFとHTMLから文字列を抽出し、文字列をチャンクに分け、文字列のEmbeddingを計算し、ベクトルDBに格納し、クエリでベクトル検索し、検索結果をLLMと融合させる必要がある。はぁ~めんどくさ。

LanceDBを使えばベクトルDB部分はLambdaで動かせるかもしれない。そうなれば、Agents for Amazon BedrockでLambdaと連携させればイケそう。

大規模言語モデルを動かせる強いGPUを持っているなら、LangChainとかいうのを使っても良い。LanceDBと連携させられる。