日曜日, 7月 13, 2025
No menu items!
ホーム ブログ ページ 6702

PGlite + pgvector で100行で実装するベクトル検索 (node/deno/drizzle)



pglite + pgvector で文章の類似度検索を実装します。

動機

とにかく手っ取り早くローカルにデータを突っ込んでおいて検索する RAG の雛形がほしかったんですが、調べても大規模ストレージを前提とした大掛かりな実装が多いです。

スクリプトを書いたらポンと実行できるセットアップ不要なものがあると、色々と実験ができます。

mastra/rag を読んでたら、簡単にできる気がしたのでやりました。ただ、chunk のドキュメント分割相当のものはまだ作ってません。そこまで難しい概念でもないので、雑に作れそうではあります。

qrdrant も検討しましたが、サーバーを建てるのが面倒でした

https://qdrant.tech/

準備: ベクトル化用の関数

今回は @ai-sdk/openai を使ってベクトル化をします


import { openai } from "@ai-sdk/openai";
import { embed } from "ai";
async function generateEmbedding(value: string): number[] {
  const { embedding } = await embed({
    model: openai.embedding("text-embedding-ada-002"),
    value,
  });
  return embedding;
}

この関数は text-embedding-ada-002 で文字列を 1536次元(長)の配列に変換します。

同じ次元のベクトル同士の類似(コサイン類似度)を取ることで、文字列同士の n 次元上の距離を測ることができます。

ここの実装はORAMAでも何でもいいですが、トランスフォーマー上の意味ベクトルの生成なので、同じモデルの同じ次元で embedding を作る必要があります。

pglite + pgvector のセットアップ

deno の node 互換モードで実装したので node でも動くはず。

pglite でデータベースを初期化します。wasmなのでドライバが不要、dataDir が与えられていないのでインメモリにストレージを展開します。


import { PGlite } from "@electric-sql/pglite";
import { vector } from "@electric-sql/pglite/vector";
import { openai } from "@ai-sdk/openai";
import { embed } from "ai";


const pglite = new PGlite({
  extensions: { vector },
  
});
await pglite.exec("CREATE EXTENSION IF NOT EXISTS vector;");


await pglite.exec(`
  CREATE TABLE IF NOT EXISTS memory (
    id SERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    embedding vector(1536)
  );
  CREATE INDEX ON memory USING hnsw (embedding vector_cosine_ops);
`);

CERATE EXTENSION vector で初期化します。text-embedding-ada-002 に合わせて 1536次元で vector 型を初期化します。

セットアップができたので、データを挿入して文書同士の距離を検索します。


async function generateEmbedding(value: string) {
  const { embedding } = await embed({
    model: openai.embedding("text-embedding-ada-002"),
    value,
  });
  return embedding;
}


const seedData = [
  "red apple",
  "green tea",
  "yellow banana",
  "pink blossom",
  "black cat",
];
for (const content of seedData) {
  const embedding = await generateEmbedding(content);
  const vec = JSON.stringify(embedding);
  await pglite.exec(
    `INSERT INTO memory (content, embedding) VALUES ('${content}', '${vec}');`
  );
}


const queryVec = await generateEmbedding("fruit");
const searchVec = JSON.stringify(queryVec);
const res = await pglite.exec(`
  SELECT
    content,
    embedding  '${searchVec}' AS distance
  FROM memory
  ORDER BY distance ASC
  LIMIT 2;`);

console.log(res[0].rows);




文書同士の距離が小さいほうが似ていることになります。一応入力から fruit っぽいものが上位にでていますね。

今回は文書が短すぎてあまり意味のある検索ができていませんが、もう少し長い文書を食わせると精度がよくなります。

pglite + pgvector

同じことを drizzle ORM でやります。

ただ、migration をスキップするために直接スキーマを定義します。ここは drizzle-kit でマイグレーションするなら不要です。


import { PGlite } from "@electric-sql/pglite";
import { vector as pgVector } from "@electric-sql/pglite/vector";
import { index, integer, pgTable, vector, text } from "drizzle-orm/pg-core";
import { drizzle, type PgliteDatabase } from "drizzle-orm/pglite";
import { openai } from "@ai-sdk/openai";
import { embed } from "ai";
import { cosineDistance, sql, desc, gt } from "drizzle-orm";


async function generateEmbedding(value: string) {
  const { embedding } = await embed({
    model: openai.embedding("text-embedding-ada-002"),
    value,
  });
  return embedding;
}


export const memory = pgTable(
  "memory",
  {
    id: integer().primaryKey().generatedAlwaysAsIdentity(),
    content: text("content").notNull(),
    embedding: vector("embedding", { dimensions: 1536 }),
  },
  (table) => [
    index("embeddingIndex").using(
      "hnsw",
      table.embedding.op("vector_cosine_ops")
    ),
  ]
);

const pglite = new PGlite({
  extensions: { vector: pgVector },
  
});
await pglite.exec("CREATE EXTENSION IF NOT EXISTS vector;");


await pglite.exec(`
  CREATE TABLE IF NOT EXISTS memory (
    id SERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    embedding vector(1536)
  );
  CREATE INDEX ON memory USING hnsw (embedding vector_cosine_ops);
`);

const db = drizzle({
  client: pglite,
  schema: { memory },
});

async function insert(content: string) {
  const embedding = await generateEmbedding(content);
  await db.insert(memory).values({
    content: content,
    embedding,
  });
}
async function query(
  text: string,
  opts: {
    threshold?: number;
    limit?: number;
  }
) {
  const embedding = await generateEmbedding(text);
  const similarity = sqlnumber>`1 - (${cosineDistance(
    memory.embedding,
    embedding
  )})`;
  return db
    .select({
      id: memory.id,
      content: memory.content,
      similarity,
    })
    .from(memory)
    .where(gt(similarity, opts.threshold ?? 0.7))
    .orderBy((t) => desc(t.similarity))
    .limit(opts.limit ?? 5);
}


await insert("red apple");
await insert("green tea");
await insert("yellow banana");
await insert("pink blossom");
await insert("black cat");


const result = await query("fruit", {
  threshold: 0.7,
  limit: 5,
});
console.log(result);

(1 - 距離) なので similarity が高いほど意味が類似しています。

Drizzle上だとここがちょっとテクニカルですが、うまく型が付いてます。偉い

  const similarity = sqlnumber>`1 - (${cosineDistance(
    memory.embedding,
    embedding
  )})`;
  return db
    .select({
      id: memory.id,
      content: memory.content,
      similarity,
    })
    .from(memory)
    .where(gt(similarity, opts.threshold ?? 0.7))
    .orderBy((t) => desc(t.similarity))
    .limit(opts.limit ?? 5);

おわり

最終的にORM付きの 100 行のコードになりました。自分は今後これを使い回すと思います。

pglite から別の postgres adapter に切り替えればプロダクション移行も簡単そうではあります。

とにかくストレージのセットアップが不要なのが嬉しく、ドライバ不要の pglite のインメモリでローカルのテストが完結します。

https://zenn.dev/mizchi/articles/deno-drizzle-pglite



Source link

Views: 0

Webサイト、ハイライト、推測 – Codepen


私はヘンリーのガイドが大好きです: ウェブサイトの作り方

貴重な小さなものがあります 最近の ウェブの原材料だけを使用して、完全に優れたウェブサイトであるWebサイトを構築する方法に関する教育資料。 HTML&CSS、そのまま。しかし、それがヘンリーのガイドについてではありません。 Webデザインをはるかに超えている全体的なアドバイスに満ちています:

あなたが人々と仕事をしているとき、親切で好奇心and盛で謙虚になり、彼らの間違いをさらに寛容にしてください。だから、あなたが自分で作ることが必然的に来るとき、おそらくあなたのために金庫にいくつかの善意があります。

🙏

私はあなたが認識の欠如だけではないのではないかと疑っています できる 手書きの生のhtmlとcssだけでウェブサイトを構築しますが、もっと それでは何? 状況。もちろん、Codepenはそれが良い選択肢であり、それを毎日より良くするために取り組んでいる場所であることを望んでいます。しかし、その地元で作成されたHTML WebサイトをVercel、Netlify、GitHubページなどのような実際のWebサイトに届けるためのオプションがたくさんあります。 Webサイトを制作するためのDevOpsの知識が、実際にWebサイトを作成するのと同じくらいの知識がある場合、それは残念です。

ああ、これは私にロブ・オーウェンによって捨てられた素晴らしい用語を考えさせます: ハンドスローフロントエンド。 彼は、粘土からボウルを作る陶器のプロセスとウェブサイト制作のプロセスを比較しています。

だから、私にとって、 フロントエンド ボウルメイキングプロセスは、周期的で非線形のプロセスです。表面レベルでは、おそらくレゴのレンガを組み立てるのと同じくらい効率的ではないように思われますが、時間の経過とともに、より多くのボウルを作り、スキルが習得に向かって増加するにつれて反復の各段階が徐々に短くなります。

それが私の芸術学士が大学で焦点を合わせていたので、私は良い陶器の類推の吸盤です。

おお!そして、陶器といえば、シャーロット・ダンを見たことがありますか 陶器 プロジェクト?それはおそらく私が今まで見た私のお気に入りの生成アートプロジェクトです。この表面は完全にコード生成されています:

本物の刻まれた艶をかけられた粘土の写真であるかもしれません。

Oooooo Tricky CSS Challenge! 歪んだハイライト。 Vadim Makeevはうまくやった:

あなたは思うだろう transform: skew() どういうわけか関与するでしょうが、特に任意のラインブレークなどでは、そのための素晴らしい機会はありません。代わりに、背景は複数の勾配の背景(超賢い)で作成され、幸いなことに box-decoration-break: clone; ライン全体で合理的に見えるようにします。


CSSセレクター構造のわずかな変化と、それが選択にどのように影響するかについて考えていますか? そのパーティーにサインアップしてください! ブラモは比較します:

.a .b .c { }
/* Versus! */
.a :is(.b .c) { }

彼らは同様に見え、振る舞いますが、前者は「CはBの子はAの子です」状況をしっかりと施行していますが、後者はCがbの子であるAの子である状況を可能にします。それは言葉で理解するのが難しすぎるので、ここにイメージがあります:

確かに私は手を伸ばしません :is() それはすべてですが、ネイティブCSSネスティングがここにあり、すべてのネストされたセレクターがシンボルから始まる必要があるので、使用が少し上がるかもしれないと思います。したがって、要素セレクターをネストしたいという簡単な方法は、 :is()

header {
  h1 { } /* nope */
}

header {
  :is(h1) { } /* yup */
}

今後の技術トレンドを楽しみにしている開発者の小さなコレクションで終わりましょう。もちろん、これは人気があります 1月 そしてそれはそうです 行進 今、しかし、私は時々少し遅いです。



Source link

Views: 0

マヤ文明の彩色された「祭壇」を新発見!作ったのはマヤ人じゃなかった


今日の中米グアテマラにかつて栄えていた古代マヤ文明の都市・ティカル(Tikal)

その遺跡で新たに、赤・黒・黄色で彩色された石造りの「祭壇」が発見されました。

西暦300年代後半のものとされ、中央メキシコに伝わる”嵐の神(Storm God)”によく似た図像が描かれていたと、米ブラウン大学(Brown University)の考古学研究チームは報告しています。

研究の詳細は2025年4月8日付で学術誌『Antiquity』に掲載されました。

目次

  • 西暦300年頃の精巧な「祭壇」を復元!
  • 古代メキシコの人々によって作られたもの?

西暦300年頃の精巧な「祭壇」を復元!

「神様の顔が描かれた石の箱のようなものが土中から発掘された」と聞いたら、まるで冒険小説の始まりのように感じるかもしれません。

しかし、それはまさに今回の発見を言い表しています。

発掘された場所は、グアテマラ北部のジャングルに位置する古代の都市遺跡・ティカル。

マヤ文明最大級の遺跡の一つであり、神殿や宮殿が林立するその中心地からわずか125メートルの地点に、これまで未発見だった遺構が眠っていたのです。

画像
祭壇が見つかった場所周辺の3D復元マップ/ Credit: Edwin Román Ramírez et al., Antiquity(2025)

チームが新たに発見したこの遺構は、縦1.8メートル、横1.33メートル、高さ約1.1メートルの長方形の祭壇でした。

この祭壇は西暦300年代後半に造られたもので、赤、黒、黄の3色で描かれた4つのパネルで飾られています。

また頭に羽飾りを着けた人形の頭像が描かれており、その両側には盾または儀式用の装飾品が配されていました。

その実際の画像とありし日の姿が復元したものがこちらです。

画像
Credit: Edwin Román Ramírez et al., Antiquity(2025)

古代都市ティカルは紀元前850年頃に成立し、長らくは影響力の乏しい小都市でしたが、西暦100年頃には王朝として急成長しています。

しかし調査の結果、ティカルで発掘されたこの祭壇は、ティカルの人々によって作られたものではないことが明らかになりました。

祭壇を作った真の人物は、同時代に中央メキシコで栄えていた大都市・テオティワカンから来た人々だったのです。

古代メキシコの人々によって作られたもの?

チームによる詳しい調査の結果、ティカルで見つかった祭壇には、テオティワカンでしかあり得ない数々の証拠が見つかっています。

例えば、祭壇に描かれた図像はティカルのものではなく、明らかにテオティワカンに伝統的な図像のタッチだったのです。

また、パネルに描かれた羽根飾りの図像は、中央メキシコに伝わる”嵐の神(Storm God)”にそっくりだったといいます。

さらに祭壇の近くから緑の黒曜石で作られた投槍の穂先が見つかったのですが、その素材と形状はテオティワカン独自のものでした。

加えて、祭壇の内部に座った姿勢で埋葬された子供の遺骨が見つかりましたが、これもティカルではほぼ見られない埋葬形式であり、テオティワカンでは一般的なものだったのです。

これらの物的証拠から、ティカルの祭壇はテオティワカンからやってきた職人が作った可能性が高いと見られています。

画像
テオティワカンに見られる伝統的な図像のタッチ/ Credit: Edwin Román Ramírez et al., Antiquity(2025)

かねてより、ティカルとテオティワカンとの間には交易や文化交流があったことが知られていました。

ただし、両者の交流は常に平和的なものだったわけではありません。

その関係性が一変したとされるのが、西暦378年に起こった「エントラーダ(Entrada)」と呼ばれる事件です。

この年、テオティワカンの高官がティカルに到来し、当時の王を強制的に廃し、自分たちの選んだ新王を勝手に擁立して、傀儡(かいらい)政権を作ったことが石碑に記されています。

そのため、この祭壇が作られた時期はちょうどティカルとテオティワカンの仲が悪かった時期であり、祭壇もテオティワカンの人々によって強制的に作られたのかもしれません。

しかし今回のような古代文明の遺構が良好な保存状態で見つかるのは、非常に貴重なことです。

こうした考古学的遺物の調査から、謎に包まれた過去の人類史もより詳しく明らかになっていくでしょう。

全ての画像を見る

参考文献

In Guatemala, painted altar found at Tikal adds new context to mysterious Maya history
https://www.brown.edu/news/2025-04-08/altar

Archeologists find ancient child remains inside Maya altar
https://www.popsci.com/science/mayan-altar-tikal/

元論文

A Teotihuacan altar at Tikal, Guatemala: central Mexican ritual and elite interaction in the Maya Lowlands
https://doi.org/10.15184/aqy.2025.3

ライター

千野 真吾: 生物学に興味のあるWebライター。普段は読書をするのが趣味で、休みの日には野鳥や動物の写真を撮っています。

編集者

ナゾロジー 編集部



Source link

Views: 0

中国 対米関税を84%に引き上げ



中国 対米関税を84%に引き上げ

中国 対米関税を84%に引き上げ



Source link

Views: 0

ケンジは3つの建物で“同じ言葉”を告げ、3回とも願いをかなえた。彼が述べた言葉は何か? その願いは? 「ウミガメのスープ」クイズに挑戦!【レベル1】



ケンジは3つの建物で“同じ言葉”を告げ、3回とも願いをかなえた。彼が述べた言葉は何か? その願いは? 「ウミガメのスープ」クイズに挑戦!【レベル1】

“クイズ王”の古川洋平さんが代表を務めるクイズ作家集団「クイズ法人カプリティオ」が問題を出題。



Source link

Views: 0

スチームパンク×ダークファンタジーなイマーシブシム『Welcome to Brightville』ゲームプレイ映像。BGMもアクションとシンクロ



スチームパンク×ダークファンタジーなイマーシブシム『Welcome to Brightville』ゲームプレイ映像。BGMもアクションとシンクロ

スチームパンク×ダークファンタジーなイマーシブシム『Welcome to Brightville』ゲームプレイ映像。BGMもアクションとシンクロ



Source link

Views: 0

Steam市場にまつわるユーザー調査の結果を解説。オンラインセミナー「サルガクチョウサ発表会 vol.9」,4月23日に開催


001.jpg

 猿楽庁は本日,オンラインセミナー「サルガクチョウサ発表会vol.9」を4月23日に開催すると発表した。本セミナーでは,猿楽庁の小島尚也氏と多田功一氏が登壇し,2025年に行った「サルガクチョウサvol.9〜Steam市場にまつわるユーザー調査〜」の調査背景や,深堀りした内容について説明する。



Source link

Views: 0

【ホリ】『ドラゴンクエスト』スライムとはぐれメタルデザインのPc専用コントローラーが5月に発売。背面拡張ボタンや連射機能などを搭載



【ホリ】『ドラゴンクエスト』スライムとはぐれメタルデザインのPc専用コントローラーが5月に発売。背面拡張ボタンや連射機能などを搭載



Source link

Views: 0

運転士不足で列車減便「激混み」



運転士不足で列車減便「激混み」

運転士不足で列車減便「激混み」



Source link

Views: 0

「大阪・関西万博」の会場へのメインルート・大阪メトロ中央線で夢洲駅まで行ってきた – GIGAZINE



取材


2025年4月13日(日)から始まる「大阪・関西万博」は、埋め立て地である夢洲で開催されます。会場の東ゲートに隣接する形で大阪メトロ中央線・夢洲駅が設けられているので、実際にこのルートで会場へ行ってみました。

EXPO 2025 大阪・関西万博公式Webサイト
https://www.expo2025.or.jp/

大阪の大動脈である御堂筋線で本町駅にやってきました。ここから中央線に乗り換えます。御堂筋線から乗り換える場合、なかもず寄りの車両に乗っておいた方が乗り換えがスムーズです。


ホームからいったん階段を上ると万博のマスコットキャラクター・ミャクミャクが待ち構えていました。


足下の「中央線」の案内に沿って進んでいきます。このほか、案内の人も配置されていたので、迷うことはないはず。


案内に従って階段を降りて進んでいくと、中央線のホームの端に到達します。夢洲に向かうのは2番線。


4月14日(月)から7月18日(金)は子ども優先の専用列車が運行されるとのこと。


あとは、やってきた夢洲行きに乗ればOK。20分で到着です。


夢洲駅の出口は1つしかないので、最寄りの階段やエスカレーター、エレベーターで改札階へ上がります。


広く幅の取られた改札階。


券売機は12台用意されていました。


出口階段。


エスカレーターを登りながら振り返ると、松で描かれた「夢洲」の文字がありました。


出てすぐ、万博会場の東ゲートがあります。


なお、このほかに尼崎と堺にパーク&ライド対応駐車場が設置されて会場までのシャトルバスが運行されるほか、鉄道駅からも有料シャトルバスが運行されます。料金は以下の通り。

片道運賃
桜島駅 350円
大阪駅(うめきたGP) 2000円
大阪駅(マルビル) 1000円
新大阪駅 1500円
京阪中之島駅 1000円
近鉄大阪上本町駅 1200円
近鉄大阪阿部野橋駅 1200円
南海なんば駅 1300円
南海境駅 1300円
南海境東駅 1300円
JR尼崎駅 2000円
阪神尼崎駅 2000円
伊丹空港(大阪空港) 1800円
関西国際空港 2500円


なお、シャトルバスは「KANSAI MaaS」での決済が必要です。万博駅シャトルバスの乗車券購入方法は以下のPDFを参考にしてください。

万博駅シャトルバスの乗車券ご購入方法
(PDFファイル)https://www.kansai-maas.jp/static/pdf/shuttlebusver.3.pdf

この記事のタイトルとURLをコピーする



Source link

Views: 0