作って学ぶ ChatGPT Atlas

これは LayerX AI Agent ブログリレー の47日目の記事です。前回は澁井さんの 「AIエージェントで「不要な過去を忘れる」」 でした。明日はバナナ好きで社内で有名なKentaさんがEvalに関する実用的なブログを書いてくれるはずです🍌
こんにちは、公私共にBet AIしたいTomoaki(@tapioca_pudd)です。
みなさんは、どのブラウザをお使いでしょうか?
The Browser Company Dia や Perplexity Comet など、AI をネイティブに搭載したブラウザが次々と登場する中、先日 OpenAI からも ChatGPT Atlas(以下 Atlas) がリリースされました。
今回は、Atlas に関する技術ブログを参考にしながら、実際に “Atlas Like” なブラウザを自作し、その実装を深掘りしていきたいと思います。
イントロダクション
もともと私は The Browser Company が開発していた Arc が大好きでした。しかし Arc の開発が停止して以来、乗り換え先難民としてさまざまなブラウザを転々としています。
ブラウザに AI をネイティブに組み込むアプローチは、もはや避けて通れない流れでしょう。
ただし、「理想的な体験」がまだ完全に確立されておらず、各社が試行錯誤を続けている段階だと感じます。現状では、どこも決定的な覇権を握れていないのが実情でしょう。
ブラウザに AI がネイティブに組み込まれたとき、ウェブアプリケーションのUI/UXは大きく変わることが予想されます。人間が操作することが前提だったUIもLLMが操作するとなれば、美しいUIよりもシンプルで端的な命名が好まれるでしょうし、ポップアップなど複雑に奥行きのある画面なども扱いにくい可能性が高いです。
AIネイティブなブラウザを理解することは、今後より良いアプリケーションの体験を作る上で非常に重要なことだと私は考えます。そこで今回は “Atlas Like” なブラウザを自作しながら、その実装を深掘りして理解を深めていきたいと思います。
アプリの簡単な紹介
Atlas を触ったことがない方もいると思うので、軽く紹介します。
すでにご存じの方は次の章までスキップしてください。
トップページ

Atlas のデフォルトページは ChatGPT のチャット画面になっています。
この設計は近年の AI ネイティブブラウザ では主流で、Comet や Dia も同様の構成です。

ひとつ特徴的なのは、テキストエリアに入力した際の検索候補の 「Google 検索」の選択肢がプルダウンの一番下にある ことです。基本的にはWeb 検索は行わずに ChatGPT に検索クエリを渡して、ChatGPT が必要に応じて Web 検索を行うという体験設計になっています。
検索結果

検索の体験としては ChatGPT に直接質問したときと近く、タブで「検索結果の一覧」や「画像表示」などが分かれています。
検索結果を開いたとき

チャット UI が右半分に、開いたページが左半分に表示されます。この状態でページの情報をコンテキストに渡しながらチャットを続けられるようになっています。
ここまでがブラウザの基本的な検索の操作です。
Agent Mode
Atlas のリリースにも特徴がいくつか紹介されていますが、個人的に注目したいのはAgent Modeです。

GUとUNIQLOでグレーのパーカを調べておすすめを教えてもらうタスク(画質悪くてすいません)
いわゆる ChatGPT の「Agent Mode」ですが、特徴的なのは Agent がヘッドレスブラウザを裏で操作するのではなく、Atlas 上の実ブラウザを操作しながらタスクを遂行する点です。また操作を中断してユーザー自身が操作を奪うこともできます。
最近は、どのブラウザでも「ページ内容に対して対話的に質問する」機能を備えています。
一方で、Agent Mode のように ブラウザ操作そのものを自然言語で制御する というアプローチは、"computer use" や "browser use" の領域で今まさにホットなトピックです。
ただしこの分野はまだ試行錯誤の段階で、最適解が見つかっていない印象を受けます。
筆者としては、このような「自然言語でブラウザを操作する体験」は今後当たり前になると考えています。そして、あらゆるアプリケーション操作が Agent によって代替される未来も遠くないでしょう。
そこで今回は、Agent Mode を実現するためのアーキテクチャ に注目しながら、Atlas を深掘りしていきたいと思います。
ChatGPT Atlas のアーキテクチャ
上記のブログを参考に、Atlas の内部構造を探っていきましょう。
OWL (OpenAI’s Web Layer)
Atlas は Chromium ベースのブラウザ ですが、デスクトップアプリの内部構成としては Atlas と OWL (OpenAI’s Web Layer) という2つのアプリで構成されています。Chromium は OWL の中に内包される形になっており、Atlas 本体は ネイティブの SwiftUI アプリ でChromium を直接組み込みません。

How we built OWL, the new architecture behind our ChatGPT-based browser, Atlasより引用
Chromium をメインプロセスから完全に分離することで、Atlas 自体の構造がシンプルにし、パフォーマンス改善・トラブルシューティング・開発効率向上 といったメリットを得ることが狙いのようです。
抽象化すると、Atlas は OWL のクライアントであり、Chromium ブラウザプロセスは OWL のホストにあたります。それぞれのプロセスはIPC(プロセス間通信)によってコミュニケーションしており、Chromiumプロジェクトで使われているIPCフレームワーク Mojoをアプリ外部の SwiftUI アプリから使うために、Swift/TypeScript バインディングを自作することで実現されてるようです。
「Agent Mode」では、AI が自動的にブラウザを操作しますが、これはAtlas からOWL client libraryを通じて OWL のブラウザプロセスに対し、IPC を通じて UI 操作(例:render、input、navigate、zoom, reload など)を指示することで実現されます。

How we built OWL, the new architecture behind our ChatGPT-based browser, Atlasより引用
Chromium の UI は通常、macOS の NSEvent などのプラットフォームイベントを Blink の WebInputEvent モデルに変換した上で、レンダラープロセスに転送します。しかし OWL では Chromium を隠れたプロセスで動かしているため、Swift クライアントライブラリ内でイベントの変換処理を行い、変換済みのイベントを Chromium へ送っているようです。
Atlasに質問して深掘りしてみる

いくつか深掘りしたい点があるのでAtlas自身に聞いてみることにします
※以下はあくまでAtlas上のChatGPTの回答であり、OpenAIの公開する一次情報ではないため、正確な情報でない可能性があります
具体的にWebViewのIPCで定義されている操作は何か?
A. 回答を抜粋すると以下
| カテゴリ | 内容 |
|---|---|
| Navigation | URL load / reload / back/forward |
| Rendering | CALayerHost への attach, bounds, compositing |
| Input | マウス/キーボード/gesture/IME |
| Zoom / Scroll | ズーム設定、スクロール |
| JavaScript / DOM | JS 実行、DOM 読み取り |
| Popup widgets | <select> や popup UI の projection |
| Focus / Editing | focus、selection、clipboard |
| Media | 音声・動画のミュート/再生 |
| Permissions | 権限プロンプトの処理 |
| Lifecycle | WebView の作成・破棄 |
| Agent mode | popup 合成、agent events、ephemeral partition |
真偽はわかりませんが、かなり細かい操作までできるようです。実際にいろんなシナリオのタスクを実験した結果、継ぎ足し継ぎ足しで操作を与えた結果のようにも見えます。
WebContentsは具体的にどのようなデータ構造でLLMに渡されているのか?
A. 回答を抜粋すると以下
WebContents の生 DOM そのままではなく、
以下を統合した 軽量で意味的な UI スナップショット構造 が LLM に渡る:
- DOM の意味要素の抽出
- Layout(座標、サイズ)
- Accessibility(role/name)
- Action 可能性(clickable, input…)
- テキスト内容
つまり “DOM/AX/Layout のハイブリッド JSON” です。
📦 最終的な全体構造(概念)
{
"url": "https://example.com",
"title": "Example Site",
"viewport": { "width": 1440, "height": 900 },
"dom": {
"tag": "html",
"children": [
{
"tag": "body",
"children": [
{
"tag": "button",
"text": "Sign in",
"layout": { "x": 210, "y": 420, "width": 140, "height": 40 },
"actions": ["click"],
"accessibility": { "role": "button", "name": "Sign in" }
}
]
}
]
}
}
真偽はわかりませんが、まずDOMをそのまま渡しているのではなくLLMが理解しやすく意味づけをしたり、物理的な位置情報を補足したり、LLMのアクション可能性を意識したものに加工されているようです。また、script, style, commentsといった情報量の少ないnodeは除外することで、ノイズやトークン消費量を減らしているようです。
ページ内のスクリーンショットは利用しているのか?
A. 回答を抜粋すると以下
ChatGPT Atlas は、LLM への入力として「ページ内のスクリーンショットを利用している」。
さらに popup ウィンドウなども合成した単一の画面画像を生成し、それをモデルに与えている。
これは OpenAI 公開ブログにより明確に裏づけられている。
真偽はわかりませんが、視覚的コンテキストを正確に取得するためにスクリーンショットは利用しているようです。見落としていたのですが、ブログ内でも<select> などプルダウンが別ウィンドウに描画されると画像に写らないためAtlas が内部で popup ウィンドウを合成した “視覚的に正しい画面画像” を生成している、と記載しているのでやはり利用しているのは間違いなさそうです。
実装してみる
仕組みがなんとなーくわかったところで、実際に作ってみましょう。
SwiftUI や AppKit で実装する気持ちになれなかったので、今回は Electron で実装していきます。
※以下はあくまで筆者がここまでの情報をもとに想像で実装した“Atlas Like”なブラウザです。vibeで実装しているので参考程度に見てください。
ブラウザの情報は以下のようにして、セマンティックに変換しています。
export function generateSemanticDOM(): SemanticDOM {
const title = document.title;
const url = window.location.href;
// Step 1: Collect all potentially interesting elements
const allElements = collectElements();
// Step 2: Filter by visibility
const visibleElements = allElements.filter(node => node.isVisible);
// Step 3: Linearize in visual order
const sortedElements = linearizeByVisualOrder(visibleElements);
// Step 4: Generate semantic text
const semanticText = generateSemanticText(sortedElements);
// Step 5: Extract actionable nodes
const actionMap = extractActionableNodes(sortedElements);
return {
title,
url,
semanticText,
actionMap,
};
}
generateSemanticTextではprefixとして以下のLLMのアクションを定義しています。最初なぜかtextareaがうまく作られておらず、検索が一生できないという状態でしたが、無事定義してあげることで検索がうまくされるようになりました。
function getSemanticPrefix(type: string): string {
const prefixMap: Record<string, string> = {
'heading': '##',
'button': '[BUTTON]',
'link': '[LINK]',
'input': '[INPUT]',
'textarea': '[TEXT AREA]',
'select': '[SELECT]',
'image': '[IMAGE]',
'text': '-',
'container': '',
};
return prefixMap[type] || '';
}
また、IPCは以下のように定義しました
// IPC channel constants (inlined to avoid module resolution issues in preload context)
const IPC_CHANNELS = {
GET_SEMANTIC_DOM: 'kernel:get-semantic-dom',
EXECUTE_ACTION: 'kernel:execute-action',
RUN_AGENT: 'agent:run',
GET_API_KEY: 'agent:get-api-key',
NAVIGATE_TO: 'ui:navigate-to',
GO_BACK: 'ui:go-back',
GO_FORWARD: 'ui:go-forward',
RELOAD: 'ui:reload',
} as const;
Atlas同様、GUとUNIQLOでグレーのパーカーについて調べてもらいました。

動かしてみると、UNIQLOについて調べたところで力尽きていました。タスクをPlanしそれを遂行するような処理が必要のようです。また、何度か試していると存在しないnodeにclick処理をしようとしてクラッシュする事象も見られました。
ブラウザの情報はかなり複雑なので、操作時にエラーが起きることを前提にリカバリーのハンドリングを入れると同時に、最初に立てた計画を遂行するまで粘り強く調べ続けるようにAgentを設計することが、安定したブラウザAgentに必要不可欠のようですね。
もっと色々実装して挙動の変化を試してみたいところですが、キリがなさそうなので今回はここまでとします。
おわりに
今回はChatGPT Atlasを参考にAgenticなブラウザに想いを馳せてみました。
今回はAgent Modeを中心に深掘りしましたが、他にもブラウザのUIの選択範囲から直接chatGPTを呼び出すアシスト機能や、過去のチャットをメモリーとして保持してコンテキストを引き継いでくれる機能なので他にも魅力的な機能がたくさんあって非常に作り込まれているなと感じました。
ブラウザとしては正直個人的にはまだデフォルトブラウザに設定するまでにはいたらないかなという感じですが、ブラウザの体験が大きく変わる過渡期に今いるのだなと強く感じました。
これからのソフトウェアはAgenticなブラウザ上でAgenticなアプリケーションが協調するような設計が求められるようになり、アプリケーション開発者としてはアプリで頑張らなくてもいいこともたくさん出てきそうですね。
Discussion