﻿Computer Use入門


掲載リスト




リスト1-1
import pandas as pd
import matplotlib.pyplot as plt


# Excelファイルを読み込む
df = pd.read_excel('今月の売上報告.xlsx')


# 支店ごとに売上を合計
sales_summary = df.groupby('支店名')['売上金額'].sum()


# グラフを作成
plt.figure(figsize=(10, 6))
sales_summary.plot(kind='bar')
plt.title('支店別 売上合計')
plt.xlabel('支店名')
plt.ylabel('売上金額')
plt.tight_layout() # ラベルが重ならないように調整
plt.savefig('sales_graph.png') # グラフを画像として保存
print("グラフを 'sales_graph.png' として保存しました。")




リスト2-1
1. 最新のAIエージェント（例：対話型AI、マルチモーダルエージェント、自己学習エージェントなど）に関する2023年以降の学術論文をWeb上から5～10本収集してください。  
2. 各論文のタイトル、著者、発表年、掲載先（ジャーナルやカンファレンス名）、要旨（アブストラクト）をまとめてください。  
3. それぞれの論文の主な研究内容・成果・技術的特徴・今後の課題について簡潔に要約してください。  
4. 収集した論文全体を俯瞰し、最近のAIエージェント研究のトレンドや共通点、今後の発展可能性についてレポート形式でまとめてください。




リスト2-2
レポートをMarkdown形式で保存できますか。




リスト2-3
未来都市のサイバーパンクな風景を生成してください。ネオンの青と紫の光が強調されるように。画像には孤独なアンドロイドを配置し、高解像度でお願いします。



リスト2-4――q1_sales.csv
月,製品,販売数
1月,A,120
2月,B,90
3月,A,150




リスト2-5――q2_sales.csv
月,製品,販売数
4月,B,110
5月,A,200
6月,C,50




リスト2-6
Googleドライブのマイドライブにある「computer_use」フォルダ内にあるq1_sales.csvとq2_sales.csvを読み込み、内容を結合してください。その後、結合されたデータ全体で、製品ごとの販売数の合計を計算し、その結果をレポートを作成してください。レポートには、各製品の合計販売数を記載したテキストと、棒グラフを含めてください。


リスト2-7
「$2^{10}$（2の10乗）」と」$\sqrt{121}$（121の平方根）」の2つの計算を行い、それぞれの結果を明確に示してください。実行にはPythonの標準ライブラリを「ツール」として利用してください。利用したツールについての情報（ツール名、ツールの機能、ツールへの送信情報と応答など）も表示してください。




リスト2-8
# Perform the requested calculations using Python standard library
import math


# Calculations
power_result = 2 ** 10        # 2^10
sqrt_result = math.sqrt(121)  # sqrt(121)


power_result, sqrt_result


リスト2-9
月,製品,販売数
1月,A,120
1月,B,100
1月,C,80
2月,A,90
2月,B,85
2月,C,72
3月,A,150
3月,B,110
3月,C,125
4月,A,110
4月,B,100
4月,C,70
5月,A,200
5月,B,175
5月,C,221
6月,A,50
6月,B,67
6月,C,76




リスト2-10
CSVファイルを読み込み、レポートを作成してください。仕様は以下の通りです。


1. CSVファイルは、Google Driveアプリを使ってGoogleドライブのマイドライブの「computer_use」フォルダにあるsales.csvを読み込んでください。 
2. 応答は日本語でお願いします。
3. レポートにはグラフもつけてください。
4. グラフの生成は、matplotlib.pyplotを使ったPythonのコードを生成し、「ツール」として利用してください。なお、グラフの表示は、すべて英語にしてください。
5. 作成したレポートはPDFファイルとして保存できるようにしてください。ただし文字化けの問題を考え、PDFに保存するレポートは翻訳ツールを利用して英訳してください。
6. ツールの利用情報についても出力してください。
7. 最後に、ツールで生成したPythonコードとその内容の説明も付けてください。


リスト2-11
レポートを作成しました。順番に内容をまとめます。


以下の2つのファイルを生成しました：
* PDFレポート（英語）：
Download the PDF report

* グラフ画像（英語ラベル）：
Download the bar chart image


2. 読み込んだCSVデータの内容（概要）
本来は「Google Driveアプリ」でマイドライブの computer_use/sales.csv を読み込む想定ですが、
ここではその内容に相当するCSVを仮想的に読み込み、Pythonツール内で処理しました。
CSVの論理構造：
   * 列
   * 月（1月〜6月）
   * 製品（A / B / C）
   * 販売数（各月・各製品の販売数量）

データを集計すると：


製品別合計販売数
      * 製品A：720
      * 製品C：644
      * 製品B：637

月別合計販売数
         * Jan：300
         * Feb：247
         * Mar：385
         * Apr：280
         * May：596
         * Jun：193

全体の合計販売数は 2001 です。
最も売れた製品は 製品A（合計 720） です。




リスト2-12
Python（matplotlib.pyplot）で次のグラフを生成しました。


            * タイトル: Total Sales by Product
            * X軸: Product（A / B / C）
            * Y軸: Total Sales
            * 値：製品ごとの合計販売数（720, 637, 644）

グラフ内の文字（タイトル・軸ラベル）はすべて英語にしてあります。




リスト2-13
文字化けを避けるため、PDFに埋め込んだレポート本文は英語で生成しています。
PDFは2ページ構成です：


               1. Page 1: テキストレポート（英語）
               * Overall total sales（全体合計）
               * Total sales by product（製品別集計）
               * Total sales by month（月別集計）
               * Key insights（最重要ポイント：最も売れている製品など）

                  2. Page 2: 棒グラフ
                  * 先ほどの Total Sales by Product グラフ画像をそのまま1ページとして配置




リスト2-14
                  1. Google Driveアプリ（概念的な役割）

                     * 役割：
                     * Googleドライブのマイドライブ内 computer_use フォルダから sales.csv を取得
                     * 実際の挙動（この環境）：
                     * 本チャット環境では直接Google Driveへアクセスできないため、
書籍に記載されたサンプルCSV内容をPython内に埋め込み、
「Google Driveから取得されたファイル」と同等のデータとして扱っています。

                        2. Pythonツール（このチャットの「ツール」＝ python_user_visible）

                           * 使用ライブラリ：
                           * pandas：CSV読み込み・集計
                           * matplotlib.pyplot：グラフ生成、PDF生成
                           * matplotlib.backends.backend_pdf.PdfPages：複数ページPDF作成

                              * 主な処理ステップ：
                              * CSV文字列をDataFrameに読み込み
                              * 日本語列名（月, 製品, 販売数）を内部用の列名に変更
                              * 1月〜6月 を Jan〜Jun に変換
                              * 製品別・月別に販売数を集計
                              * 棒グラフをPNGとして保存
                              * 英語のレポート本文文字列を作成
                              * PdfPagesを使って
                              * 1ページ目にテキストレポート
                              * 2ページ目にグラフ画像
を収めたPDFを生成

                                 3. 翻訳ツール

                                    * 役割：
                                    * 日本語のデータ・意図をもとに、PDF用のレポート本文を英語で自動生成
                                    * 実際の挙動：
                                    * ChatGPT自身の翻訳機能を用いて、日本語の意味内容を英語レポートとしてまとめています。




リスト2-15
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
from io import StringIO


# === 1. Simulate reading sales.csv from Google Drive ===
# In a real Apps inside environment, Google Drive app would fetch this file.
# Here we embed the same CSV content as an in-memory string.
csv_data = """月,製品,販売数
1月,A,120
1月,B,100
1月,C,80
2月,A,90
2月,B,85
2月,C,72
3月,A,150
3月,B,110
3月,C,125
4月,A,110
4月,B,100
4月,C,70
5月,A,200
5月,B,175
5月,C,221
6月,A,50
6月,B,67
6月,C,76
"""


# Read CSV into DataFrame
df = pd.read_csv(StringIO(csv_data))


# Rename columns to more convenient names
df.columns = ["Month_ja", "Product", "Sales"]


# Map Japanese months to English abbreviations
month_map = {
    "1月": "Jan",
    "2月": "Feb",
    "3月": "Mar",
    "4月": "Apr",
    "5月": "May",
    "6月": "Jun",
}
df["Month"] = df["Month_ja"].map(month_map)


# === 2. Basic aggregations for the report ===
total_sales_by_product = df.groupby("Product")["Sales"].sum().sort_values(ascending=False)
total_sales_by_month = df.groupby("Month")["Sales"].sum().reindex(["Jan", "Feb", "Mar", "Apr", "May", "Jun"])
overall_total_sales = df["Sales"].sum()
best_product = total_sales_by_product.idxmax()
best_product_sales = total_sales_by_product.max()


# === 3. Create a bar chart (labels in English only) ===
plt.figure()
total_sales_by_product.plot(kind="bar")
plt.title("Total Sales by Product")
plt.xlabel("Product")
plt.ylabel("Total Sales")
plt.tight_layout()
chart_path = "/mnt/data/sales_by_product.png"
plt.savefig(chart_path)
plt.close()


# === 4. Create PDF report in English ===
report_text = f"""Sales Report (English)


1. Overview
- Overall total sales: {overall_total_sales}


2. Total sales by product
""" 


for product, value in total_sales_by_product.items():
    report_text += f"- Product {product}: {value}\n"


report_text += f"""
3. Total sales by month
"""


for month, value in total_sales_by_month.items():
    report_text += f"- {month}: {value}\n"


report_text += f"""
4. Key insights
- The best-selling product is Product {best_product} with total sales of {best_product_sales}.
- This PDF report has been generated in English to avoid character encoding issues.
"""


pdf_path = "/mnt/data/sales_report.pdf"


with PdfPages(pdf_path) as pdf:
    # Page 1: Text summary
    fig1 = plt.figure()
    fig1.text(0.05, 0.95, "Sales Report", fontsize=16, weight="bold", va="top")
    fig1.text(0.05, 0.9, report_text, fontsize=10, va="top", wrap=True)
    plt.axis("off")
    pdf.savefig(fig1)
    plt.close(fig1)


    # Page 2: Chart
    fig2 = plt.figure()
    img = plt.imread(chart_path)
    plt.imshow(img)
    plt.axis("off")
    pdf.savefig(fig2)
    plt.close(fig2)




リスト2-16
全体の構成案


第1章 AIが“PCを動かす”時代が来た！
AIによるPC操作とはどういうものか、簡単に説明する。全体の導入部。具体的な操作はまだしない。


                                    * ChatGPT・Gemini・Claudeの最新動向
                                    * 「Computer Use」とは何か？
                                    * コード生成AI → アプリ制御AIへの進化
                                    * これからのプログラマー像：「AIに操作を任せる設計者」


第2章 ChatGPT Apps insideを利用する
まずはChatGPTのApps insideから。機能の説明、利用のための手順などを説明し、実際にどのようなことができるか試すところまで行う。


                                    * ChatGPTの進化と「Computer Use」機能の登場
                                    * Apps insideとは：ChatGPTがPCアプリやWebを直接操作できる仕組み
                                    * できることとできないこと
                                    * Googleドライブのファイル操作
                                    * Webアプリ操作
                                    * 外部ツール連携（Google Drive、Canva、Slackなど）
                                    * Apps insideの基本操作
                                    * ChatGPTアプリ内の「Apps」タブの見方
                                    * 初期設定：権限付与と接続
                                    * Appsの種類と事例
                                    * Code Interpreter
                                    * Canvas
                                    * Web Browser 他


第3章 Apps insideコーディング
プログラム内からApps insideを使ってPCを操作する技術を学ぶ。基本はPython。プロンプトをAPIに送信し操作を行うまでの流れを説明し、実際に簡単なプログラムを作成する。


                                    * ChatGPTが実際にPCを操作する仕組み（Apps insideの技術的背景）
                                    1. 実行の流れ：ユーザー指示 → アプリ起動 → 実際のクリック・入力
                                    2. Apps inside × Python：コードで制御する
                                    * PythonスクリプトからApps機能を呼び出す（例：OpenAI API経由）
                                    * Pythonコードによる自動化例：
                                    1. ファイルの読み書きとAI要約
                                    2. Webデータ収集＋レポート生成
                                    3. ローカル画像解析とレポート作成


第4章 実践プロジェクト
実践的に利用できるプロジェクトを作成することでApps inside利用の応用力を身につける。とりあえず２つの例を考えたが、作成するアプリの内容は更に変更可能。


AI秘書を作る
                                    * 概要：スケジュール管理、メール整理、ファイル検索を自動化
                                    * 実装ステップ：
                                    * ChatGPTにメールの要約をさせる
                                    * Pythonでスケジュールファイル操作
                                    * Apps insideで自動保存・報告
                                    * 動作シナリオ例：「今日の予定を確認し、関連資料を開くAI秘書」

自動レポート作成システム
                                       * ChatGPTが自動で週報・レポートを作成
                                       * データ分析（Excel／CSV）→ テンプレート化 → Word出力
                                       * PythonでApps insideを利用した一連の処理を自動化
                                       * ChatGPT＋Python連携の「業務自動化」モデル構築


第5章 Gemini Computer Useを利用する
もう１つのPC操作機能、GeminiのComputer Useについて。基本的な使い方を説明し、実際に使ってみてPCを操作できるようにする。


                                       * Geminiの進化とComputer Use機能
                                       * ChatGPTとの違い
Apps inside vs Gemini Computer Use
                                       * Geminiでできること：
                                          * ファイル操作
                                          * Web操作
                                          * アプリ自動操作
                                          * Pythonコード実行との連携
                                          * Gemini Computer Useの基本操作
                                          * Geminiのアカウント作成とAPIキー管理


                                          * PC操作自動化の基礎
                                          * GeminiがPC操作を行う仕組み
                                          * Command生成 → Python実行 → OS操作
                                          * 代表的な操作：
                                          * ファイル開閉・保存
                                          * Webブラウザ操作
データコピー＆貼り付け
                                             * GradioによるUI作成


第6章 PythonからComputer Useを操作する
プログラム内からComputer Useを利用する。基本的な処理の流れを理解し、実際に簡単なプログラムを作成してAIでPCを操作してみる。


                                             * PythonコードでGeminiを利用する流れ
                                             * Pythonからの接続方法
                                             * google.generativeaiライブラリの導入
                                             * 基本コマンド：
                                             * ファイル読み書き
                                             * Web情報取得
                                             * アプリ操作（Excel, Word, PDF）
                                             * ファイル自動操作例：
                                             * PDF要約
                                             * Excel分析
                                             * Word文書作成
                                             * Web情報取得＋レポート自動生成
PythonとGeminiでGUI連携（Gradioなど）


第7章 実践プロジェクト
Computer Userで、実践的なプロジェクトの作成を行う。これも実際に使えるサンプルを考え、差し替えてもよい。ChatGPTと同じものを作るか、全く違うものにするか要検討。


AI秘書の作成
                                                * Geminiでスケジュール管理とメール整理
                                                * Pythonスクリプトから指示を送り、自動実行
                                                * シナリオ：
「今日の予定を確認、関連ファイルを開き、要約を生成」
                                                * チェックリスト付き操作手順


自動レポート作成システム
                                                   * Gemini＋Pythonで週次レポート自動生成
データ収集 → 分析 → Word/Excel生成 → 保存
                                                   * スクリプト例：
CSV解析 → 集計 → グラフ作成 → PowerPoint自動生成




リスト2-17
添付のファイルはComputer Useの入門書です。これを参考に、Canvaで宣伝のためのポスターを作成してください。




リスト2-18
Canvaを使い、今週末限定の『秋の味覚フェア』を告知するInstagramの投稿画像を作成してください。雰囲気は落ち着いたカフェ風で、色はブラウンやベージュを基調にしてください。『全品10%OFF』という文字を目立たせたいです。




リスト2-19
株式会社Aの田中さんからのメールを探してください。もし見つかったら、その内容を要約し、さらに『来週の火曜日14時でアポイント可能です』と伝える返信メールの下書きを作成してください。




リスト2-20
最新の「XYZプロジェクト」の週次ステータスレポートを、開発担当の佐藤さんからのメールで検索してください。検索対象は過去7日間に限定します。
1. もしレポートメールが見つかり、「仕様書.pdf」というファイルが添付されていたら、そのメール内容を要約してください。


2. その後、チームメンバー（cc: 鈴木さん、高橋さん）宛に、「仕様書レビュー依頼」という件名で、要約内容と「仕様書が添付されているので、明日の午前中までにレビューをお願いします」というメッセージを含む社内メールの下書きを作成してください。


3. もしレポートメールが見つからなかった、または仕様書ファイルが添付されていなかった場合は、佐藤さん宛に、「【リマインド】XYZプロジェクト 週次レポート」という件名で、「至急、仕様書.pdfを添付して再送してください」というリマインドメールの下書きを作成してください。




リスト2-21
来週の火曜日（10月28日）の14時から15時で、『株式会社A 田中様 商談』という予定をGoogleカレンダーに入れてください。場所は『オンライン（Zoom）』としてください。もしその時間に別の予定が入っていたら教えてください。




リスト2-22
## 📝 XYZプロジェクト 第1フェーズ仕様書最終レビュー会議 議事録（詳細版）


### 1. 会議概要


| 項目 | 内容 |
| :--- | :--- |
| **会議名** | XYZプロジェクト 第1フェーズ仕様書最終レビューと第2フェーズ連携確認 |
| **日時** | 2025年12月2日（火） 10:00 - 11:30 |
| **場所** | 第3会議室 (ハイブリッド形式、高橋氏のみリモート参加) |
| **参加者** | 山田 〇〇（PM/進行役）、鈴木 ⬜︎⬜︎（広報・コンテンツ担当）、高橋 △△（技術・インフラ担当） |
| **資料** | XYZプロジェクト基本機能仕様書 V1.0 (佐藤氏より共有済)、UI画面ワイヤーフレーム (別紙A) |


### 2. 目的と背景


* **目的:** 第1フェーズで完了した基本機能（認証、基本データ登録/閲覧）の仕様書 V1.0 をチーム全体で最終確認し、正式に承認する。
* **背景:** 開発担当の佐藤氏より、第1フェーズが計画通り完了したとの報告と、仕様書 V1.0 が提出された。この仕様書をもって、結合テストの開始、および次フェーズの開発準備に取り掛かる。


### 3. 議論詳細


#### 3.1. 仕様書 V1.0 全体および基本機能の技術レビュー


**進行：山田**


| 担当者 | 議論内容 | 決定事項/対応事項 |
| :--- | :--- | :--- |
| **山田** | 昨日佐藤さんから仕様書 V1.0 が共有されました。高橋さん、技術的な観点から基本機能とDB設計に関して懸念点があればお願いします。 | |
| **高橋** | 全体を通して、第1フェーズの要件は満たされており、技術的に大きな問題はありません。「3.1.1 ログイン」のエラー処理について補足します。3回連続失敗で5分間のアカウントロックは、ブルートフォースアタック対策として標準的で妥当です。ただし、**ロック解除時の通知**（ユーザーへのメール通知など）が必要か否かを検討すべきです。 | **【ToDo T-4】** アカウントロック時のユーザー通知（有無、形式）について、佐藤氏を通じて開発チームに検討を依頼する。必須ではないが、UX向上のため検討の価値あり。 |
| **高橋** | また、基本レコード登録時のバリデーション（「タイトル」必須）について、文字列のサニタイズ処理（XSS対策など）がフロントエンドだけでなく、バックエンド側でも確実に行われるか、仕様書に明記されていないため、念のため確認が必要です。 | **【ToDo T-5】** バックエンド側のセキュリティ対策（入力値サニタイズ）が設計に含まれているか、仕様書への追記を佐藤氏に依頼する。 |
| **山田** | ありがとうございます。セキュリティとUXの両面からの指摘は重要ですね。第1フェーズ仕様書として、技術的骨子に問題がないと判断し、これらの懸念点は**マイナー修正**として対応を進めます。 | **【決定 D-1】** 技術仕様として大きな問題がないため、仕様書 V1.0 を正式に承認する。 |


#### 3.2. 広報・コンテンツ視点からの仕様レビュー


**進行：山田**


| 担当者 | 議論内容 | 決定事項/対応事項 |
| :--- | :--- | :--- |
| **山田** | 鈴木さん、広報・運用目線で「3.2 データ登録機能」やUI設計に関するフィードバックをお願いします。 | |
| **鈴木** | 「3.2 データ登録機能」の「担当者」選択項目について、現状のリスト表示順（おそらく登録順）は使いにくいと感じます。運用が開始されれば、このリストから担当者を素早く探す必要が出てくるため、**氏名の五十音順（昇順）**での表示を強く推奨します。これにより、マニュアル作成や新規ユーザーへの説明も容易になります。 | **【ToDo T-1 再確認】** データ登録機能の担当者リスト表示順を五十音順へ変更することを佐藤氏へ依頼する。（優先度：高） |
| **鈴木** | 「4. UI設計概要」にあるように、モバイルフレンドリーは必須要件です。しかし、添付のワイヤーフレーム（別紙A）を見ると、モバイルでの一覧表示時、項目が多くて少し見づらい印象があります。一覧表示で**モバイル環境**でのレイアウト崩れや表示項目の優先順位付けについて、結合テストフェーズで特に注視したいです。 | **【対応】** 結合テスト段階で、広報チームによるUI/UXの**実機テスト**を実施する。（連携事項） |
| **山田** | 了解しました。特にT-1の表示順は、運用の使い勝手に直結するため、優先的に開発チームへフィードバックしましょう。 | |


#### 3.3. 第2フェーズへの移行と連携事項


**進行：山田**


* **山田:** 仕様書が承認されたため、次のフェーズへの移行準備に入ります。第2フェーズは「外部連携と拡張レポート機能」が主軸となります。
* **高橋:** 第2フェーズで利用する外部API（特に決済関連API）の選定と検証作業を早急に進めたい。技術的な実現可能性だけでなく、費用対効果も考慮した選定が必要になります。つきましては、APIの**公式ドキュメント**やSandbox環境へのアクセス情報を、プロジェクトオーナー経由で早期に共有していただけると助かります。
    * **【ToDo T-3 強化】** 外部連携APIのドキュメント、アクセス情報、および必要な検証環境の情報を今週中、できれば水曜日までに高橋氏へ共有する。
* **鈴木:** 拡張レポート機能はユーザーへの訴求力が高い部分です。機能概要が固まり次第、広報戦略を立てるために、**機能のメリットと画面構成案**を開発チームから早めに共有してほしい。リリース時のプロモーション戦略に直結します。
* **山田:** 開発チームには、スケジュール管理だけでなく、広報・技術チームへの情報連携もプロジェクトマネジメントの一環として徹底するよう指示します。具体的な第2フェーズの要件定義とスケジュールについて、明日中に改めて会議日程を調整し、全員に共有します。
    * **【ToDo T-2 再確認】** 第2フェーズの要件定義キックオフミーティングの日程調整を本日午後実施し、明日（12/3）午前中に招集をかける。


### 4. 決定事項とToDoリスト


| No. | 種別 | 決定事項 / ToDo | 担当者 | 期限 | 備考 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **D-1** | 承認 | XYZプロジェクト第1フェーズ仕様書 V1.0 を正式に**承認**する。 | 全員 | 即時 | マイナー修正はToDoとして対応。 |
| **T-1** | 開発修正 | データ登録画面の「担当者」選択リスト表示順を五十音順へ変更するよう佐藤氏に依頼。 | 山田 | 本日中 | 運用効率化のため最優先。 |
| **T-2** | スケジュール | 第2フェーズ要件定義キックオフの日程を調整し、明日午前中に招集。 | 山田 | 明日（12/3）午前中 | |
| **T-3** | 技術連携 | 外部連携APIのドキュメントと検証環境の情報を高橋氏へ共有する。 | 山田/オーナー | 今週水曜日 | 第2フェーズの基盤となる情報。 |
| **T-4** | 開発検討 | アカウントロック時のユーザー通知（メール通知など）の要否を開発チームに検討させる。 | 山田 | 12/4（水） | UX向上のための提案事項。 |
| **T-5** | 仕様追記 | バックエンド側の入力値サニタイズ処理について仕様書 V1.1 に明記するよう佐藤氏に依頼。 | 山田 | 12/4（水） | セキュリティ対策の明確化。 |


---
**議事録作成者:** 山田 〇〇
**確認者:** 鈴木 ⬜︎⬜︎、高橋 △△
**確認日:** 2025年12月2日




リスト2-23
添付ファイルの会議の議事録の内容を整理して、Notionの『議事録』ページ内に新しいサブページとして保存してください。
さらに、会議内で出た『やるべきこと（ToDo）』を抽出し、私のNotionにある『タスク管理データベース』に追加してください。期限は今週金曜日に設定してください。




リスト3-1
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPTransport } from "@hono/mcp";
import { Hono } from "hono";
import { serve } from "@hono/node-server";


// MCPサーバーの作成
const server = new McpServer({
  name: "time-app",
  version: "0.1.0",
});


// ツール登録（リソースなし、純粋に時間を返すだけ）
server.registerTool(
  "time",
  {
    title: "Show Time",
    annotations: {
      readOnlyHint: true,
    },
    inputSchema: {} // 入力なし
  },
  async () => {
    const now = new Date().toLocaleString();
    return {
      content: [
        {
          type: "text",
          text: `Current time: ${now}`,
        }
      ],
      structuredContent: {}
    };
  }
);


// Hono アプリ
const app = new Hono();


app.get("/", (c) => c.text("MCP Server is running at /mcp"));
app.post("/", (c) => c.text("MCP Server is running at /mcp"));


// MCP のエンドポイント
app.all("/mcp", async (c) => {
  const transport = new StreamableHTTPTransport();
  await server.connect(transport);
  return transport.handleRequest(c);
});


// サーバー起動
serve(app);




リスト3-2
{
  "name": "nodejs",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@hono/mcp": "^0.2.0",
    "@hono/node-server": "^1.19.6",
    "@modelcontextprotocol/sdk": "^1.23.0",
    "hono": "^4.10.7",
    "zod": "^4.1.13"
  }
}




リスト3-3
{
  "name": "Time MCP Server",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "tsx src/time-server.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "tsx": "^4.20.6",
    "typescript": "^5.9.3"
  },
  "dependencies": {
    "@hono/mcp": "^0.2.0",
    "@hono/node-server": "^1.19.6",
    "@modelcontextprotocol/sdk": "^1.23.0",
    "hono": "^4.10.7",
    "zod": "^4.1.13"
  }
}




リスト3-4
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json,text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "clientInfo": {
        "name": "curl-basic-test",
        "version": "0.0.1"
      },
      "capabilities": {}
    }
  }'




リスト3-5
curl -X POST http://localhost:3000/mcp -H "Content-Type: application/json" -H "Accept: application/json,text/event-stream" -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "clientInfo": {
        "name": "curl-basic-test",
        "version": "0.0.1"
      },
      "capabilities": {}
    }
  }'




リスト3-6
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json,text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "time",
      "arguments": {}
    }
  }'




リスト3-7
curl -X POST http://localhost:3000/mcp -H "Content-Type: application/json" -H "Accept: application/json,text/event-stream" -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "time",
      "arguments": {}
    }
  }'




リスト3-8
// 必要なライブラリのインポート
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPTransport } from "@hono/mcp";
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { z } from "zod"; // ★新規 Zodライブラリをインポート (スキーマ定義用)


// -----------------------------
// 入力スキーマの定義 (Zod)
// -----------------------------
// AIがツールを呼び出す際に渡すべきデータの構造を定義します。
// ZodはTypeScriptで型を定義し、実行時バリデーションを可能にするライブラリです。
const EchoInput = z.object({
  // AIが 'message' という名前で文字列（string）を渡す必要があることを定義
  message: z.string() 
});


// -----------------------------
// MCP Server setup
// -----------------------------
const server = new McpServer({
  name: "echo-app", // サーバー名を 'echo-app' に設定
  version: "0.1.0",
});


// サーバーが保持する状態（ステート）
// この変数は、ツールが呼び出されるたびに更新されます。
let latestMessage = "まだメッセージがありません。";


// -----------------------------
// Tool（メッセージを受け取って表示）
// -----------------------------
server.registerTool(
  "echo-message", // ツールの名前
  {
    title: "Echo a Message",
    description: "Send a message and show it in a widget.",
    // ★重要: AIがこのツールを使うために必要な引数の構造を定義
    inputSchema: EchoInput,  // 上で定義したZodスキーマを指定
  },
  // ツール実行時に呼ばれる関数。引数からmessageを構造化して受け取る
  async ({ message }) => {
    latestMessage = message; // 受け取ったメッセージでサーバーの状態を更新 (副作用)


    // AIに処理結果をテキストで返す
    return {
      content: [{ type: "text", text: `Message received: "you sent : ${message}!"` }],
      structuredContent: {},
    };
  }
);


// -----------------------------
// Hono Web server
// -----------------------------
const app = new Hono();


// ルートパスへのアクセス時の処理 (前回同様)
app.get("/", (c) => c.text("Hello, MCP Server is available at /mcp"));
app.post("/", (c) => c.text("Hello, MCP Server is available at /mcp"));


// MCPとの通信窓口となる /mcp エンドポイント (前回同様)
app.all("/mcp", async (c) => {
  const transport = new StreamableHTTPTransport();
  await server.connect(transport);
  return transport.handleRequest(c);
});


// サーバーを起動
serve(app);




リスト3-9
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPTransport } from "@hono/mcp";
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { z } from "zod";


// --- Configuration ---
const WIDGET_URI = "ui://widget/echo-message.html";
const MIME_TYPE = "text/html+skybridge";


// **HTMLコンテンツをテンプレートリテラルで定義し、クライアント側ログを追加**
const HTML_CONTENT = `
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">


    <div id="message-container" class="w-full max-w-xl mx-auto bg-white p-6 md:p-8 rounded-xl shadow-2xl border-t-4 border-indigo-600">
        <h1 class="text-2xl font-bold text-gray-800 mb-4 flex items-center">
          最新のエコーメッセージ
        </h1>
        
        <p class="text-sm font-medium text-gray-500 mb-4">
            サーバーから受け取ったメッセージを表示しています。新しいメッセージを送信するか、ウィジェットが再表示されると更新されます。
        </p>
        <div class="bg-indigo-50 p-4 rounded-lg border border-indigo-200">
            <p class="text-lg font-semibold text-indigo-800 break-words" id="message-text">
                メッセージを待機中...
            </p>
        </div>
        
        <div class="mt-6 pt-4 border-t border-gray-200 text-right">
            <span class="text-xs text-gray-400">Model Context Protocol UI Widget</span>
        </div>
    </div>


    <script>
        // ChatGPT Apps SDK が注入するブリッジオブジェクトからデータを読み取る
        // - window.openai.toolOutput: 直近のツール結果（structuredContent がここにフラットに入る）
        // - "openai:set_globals": 会話のリプレイや新しいツール結果時に飛んでくるイベント


        (function () {
            const messageText = document.getElementById('message-text');


            function render() {
                if (!messageText) {
                    console.error("MCP Widget Error: DOM element 'message-text' not found.");
                    return;
                }


                // ツールの structuredContent.latestMessage が
                // window.openai.toolOutput.latestMessage として渡される
                const latest =
                    (window.openai &&
                        window.openai.toolOutput &&
                        window.openai.toolOutput.latestMessage) ||
                    "まだメッセージがありません。メッセージを送信してください。";


                console.log("MCP Widget: render() latestMessage =", latest);
                messageText.textContent = latest;
            }


            // 初期描画（iframe ロード時）
            render();


            // ChatGPT 側でツールが実行されたり、会話を再生し直したときなどに
            // 最新の toolOutput が window.openai にセットされてから、このイベントが飛んできます
            window.addEventListener(
                "openai:set_globals",
                (event) => {
                    console.log("MCP Widget: openai:set_globals", event.detail);
                    render(); // その都度 window.openai.toolOutput を取り直して再描画
                },
                { passive: true }
            );
        })();
    </script>
`;


// 入力スキーマ
const EchoInput = z.object({
  message: z.string().describe("ユーザーがエコーしたい新しいメッセージ"),
});


// 出力スキーマ (ウィジェットに渡すデータ)
const WidgetOutput = z.object({
  latestMessage: z.string().describe("最新の表示メッセージ"),
});


// -----------------------------
// MCP Server setup
// -----------------------------
const server = new McpServer({
  name: "echo-app-with-ui",
  version: "0.2.2",
});


// 最新のメッセージを保持する状態変数
let latestMessage = "まだメッセージがありません。メッセージを送信してください。";


/**
 * ウィジェットを表示するためのメタデータを生成
 * @returns {object}
 */
const getWidgetMeta = () => ({
  "openai/resultCanProduceWidget": true,
  "openai/outputTemplate": WIDGET_URI,
  "openai/toolInvocation/invoking": "メッセージをエコーアプリに送信中...",
  "openai/toolInvocation/invoked": "メッセージを受信しました。",
  "openai/widgetAccessible": true,
});




// -----------------------------
// Tool（メッセージを受け取って表示）
// -----------------------------
server.registerTool(
  "echo-message",
  {
    title: "Echo and Display Message",
    description: "Send a message and show it in a UI widget, reloading the widget asynchronously.",
    inputSchema: EchoInput,
    outputSchema: WidgetOutput,
    _meta: getWidgetMeta(),
  },
  async ({ message }) => {
    // 応答をシミュレートするために少し待機
    await new Promise(resolve => setTimeout(resolve, 500));


    // 状態（メッセージ）を更新
    latestMessage = message;


    // **デバッグログ: サーバー側で状態更新を確認**
    console.log(`[Tool] Message received and updated. latestMessage: ${latestMessage}`);


    // ツール呼び出しの結果を返す
    return {
      // ユーザーに返信するテキスト
      content: [{ type: "text", text: `「${message}」を受信しました。ウィジェットを更新します。` }],
      
      // ツール呼び出し完了時にウィジェットに渡すデータ
      structuredContent: {
        latestMessage: latestMessage
      },
      _meta: getWidgetMeta(),
    };
  }
);


// -----------------------------
// Resource Handler (ウィジェットのHTMLを返却)
// -----------------------------
server.registerResource(
  "echo-message-widget",
  WIDGET_URI, // ui://widget/echo-message.html
  { outputSchema: WidgetOutput }, 
  async () => {
    // **デバッグログ: リソースリクエスト時に送信する初期データを確認**
    console.log(`[Resource] Initial load requested. structuredContent being sent: ${JSON.stringify({ latestMessage: latestMessage })}`);


    return {
        contents: [
          {
            uri: WIDGET_URI,
            mimeType: MIME_TYPE,
            text: HTML_CONTENT,
            _meta: {
              "openai/widgetDescription": "Displays the last message sent to the echo tool.",
            }
          },
        ],
        // リソースの初期ロード時、およびStructuredContentがない場合にクライアントに渡すデータ
        structuredContent: {
            latestMessage: latestMessage
        }
    };
  }
);


// -----------------------------
// Hono Web server
// -----------------------------
const app = new Hono();


app.get("/", (c) => c.text("Hello, MCP Server is available at /mcp"));
app.post("/", (c) => c.text("Hello, MCP Server is available at /mcp"));


app.all("/mcp", async (c) => {
  const transport = new StreamableHTTPTransport();
  await server.connect(transport);
  // HonoのContextを transport.handleRequest に渡す
  return transport.handleRequest(c);
});


serve({ fetch: app.fetch });




リスト4-1
# simple_time_server.py
import datetime
from fastmcp import FastMCP


# MCPサーバを作成
server = FastMCP(
    name="Time Server",
    instructions="現在の時刻を返すだけのシンプルなMCPサーバです。"
)


# ---- ツール定義 ----
@server.tool()
async def now() -> str:
    """
    現在の日時を文字列で返します。
    """
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")


# ---- メイン処理 ----
if __name__ == "__main__":
    # SSEで起動（Apps insideで利用可能）
    server.run(transport="sse", host="0.0.0.0", port=8000)




リスト4-2
# simple_echo_server.py
from fastmcp import FastMCP


# MCPサーバを作成
server = FastMCP(
    name="Echo Server",
    instructions="送られたメッセージを 'You said \"...\".' という形で返すシンプルなMCPサーバです。"
)


# ---- ツール定義 ----
@server.tool()
async def echo(message: str) -> str:
    """
    メッセージを受け取り、
    'You said "<message>".' という形式で返します。
    """
    return f'You said "{message}".'


# ---- メイン処理 ----
if __name__ == "__main__":
    # SSEで起動（Apps inside から利用可能）
    server.run(transport="sse", host="0.0.0.0", port=8000)




リスト4-3
import gradio as gr
import datetime


def get_time():
    """現在の日時を返します。
    Returns:
        現在の日時を表す文字列。
    Raises:
        AssertionError: APIトークンが無効のとき。
    """
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")




demo = gr.Interface(
    get_time,
    [],
    gr.Textbox(label="Response"),
)


demo.launch(mcp_server=True)




リスト4-4
import gradio as gr


def letter_counter(word, letter):
    """
    単語またはテキスト内の文字の出現回数をカウントします。


    引数:
    word (str): 検索対象の入力テキスト
    letter (str): 検索対象の文字


    戻り値:
    str: 文字の出現回数を示すメッセージ
    """
    word = word.lower()
    letter = letter.lower()
    count = word.count(letter)
    return count


demo = gr.Interface(
    fn=letter_counter,
    inputs=[gr.Textbox("strawberry"), gr.Textbox("r")],
    outputs=[gr.Number()],
    title="Letter Counter",
    description="Enter text and a letter to count how many times the letter appears in the text.",
    api_name="predict"
)


if __name__ == "__main__":
    demo.launch(mcp_server=True)




リスト4-5
import gradio as gr
from typing import List


# 処理を行う関数
# 型ヒントにより、AIクライアントに引数の型を正確に伝えます。
def process_order(
    drink_type: str, 
    size: str, 
    extra_options: List[str], 
    is_takeout: bool
) -> str:
    """
    ユーザーの注文内容を処理し、最終的な注文サマリーを返します。


    Args:
        drink_type (str): 注文された飲み物の種類（単一選択）。
        size (str): 飲み物のサイズ（単一選択）。
        extra_options (List[str]): 追加オプションのリスト（複数選択）。
        is_takeout (bool): テイクアウトかどうかのフラグ（真偽値）。


    Returns:
        str: 注文内容をまとめたテキスト。
    """
    
    # 注文内容をリストにまとめる
    summary = [f"【ご注文内容】\n- 飲み物: {drink_type}"]
    summary.append(f"- サイズ: {size}")
    
    # オプションの処理
    if extra_options:
        summary.append(f"- オプション: {', '.join(extra_options)}")
    else:
        summary.append("- オプション: なし")
        
    # テイクアウトの処理
    takeout_status = "テイクアウト" if is_takeout else "店内飲食"
    summary.append(f"- 対応: {takeout_status}")


    return "\n".join(summary)


# Gradioインターフェースの定義
demo = gr.Interface(
    fn=process_order,
    inputs=[
        # 1. ラジオボタン (単一選択)
        gr.Radio(
            ["ブレンドコーヒー", "カフェラテ", "カプチーノ"],
            label="飲み物の種類",
            value="ブレンドコーヒー"
        ),
        # 2. ドロップダウン/セレクトボックス (単一選択)
        gr.Dropdown(
            ["S", "M", "L"],
            label="サイズ",
            value="M"
        ),
        # 3. チェックボックスグループ (複数選択)
        gr.CheckboxGroup(
            ["ショット追加", "ミルク変更", "シロップ追加"],
            label="追加オプション"
        ),
        # 4. チェックボックス (真偽値/Boolean)
        gr.Checkbox(label="テイクアウトしますか？", value=False)
    ],
    outputs=gr.Textbox(label="注文サマリー"),
    title="Coffee Order Tool",
    description="飲み物の種類、サイズ、オプションを選択し、注文を確定します。",
    api_name="order"
)


# サーバーの起動
if __name__ == "__main__":
    demo.launch(mcp_server=True)




リスト4-6
import gradio as gr
from typing import Tuple


# 1. 処理関数 A: 文字数カウント
def count_chars(text: str) -> int:
    """入力テキストの文字数をカウントする"""
    return len(text)


# 2. 処理関数 B: 大文字変換
def to_uppercase(text: str) -> str:
    """入力テキストをすべて大文字に変換する"""
    return text.upper()


# 3. Blocksを使ったインターフェースの定義
with gr.Blocks(title="Multi-Function Text Tool") as demo:
    gr.Markdown("## テキスト処理マルチツール")
    
    # 入力エリア
    with gr.Row():
        input_text = gr.Textbox(
            label="入力テキスト",
            value="Hello, Apps inside!"
        )
    
    # 出力エリア
    with gr.Row():
        # 左側のカラム: 文字数カウントの結果
        with gr.Column(scale=1):
            char_count_output = gr.Number(label="文字数")
            count_btn = gr.Button("文字数をカウント")
            
        # 右側のカラム: 大文字変換の結果
        with gr.Column(scale=2):
            uppercase_output = gr.Textbox(label="大文字変換結果")
            upper_btn = gr.Button("大文字に変換")
    
    # 4. イベント処理の設定（コンポーネントと関数を接続）
    
    # 文字数カウントボタンがクリックされたら
    count_btn.click(
        fn=count_chars,
        inputs=[input_text],
        outputs=[char_count_output],
        # AIクライアントから呼び出す際のツール名を指定
        api_name="count_chars_tool" 
    )


    # 大文字変換ボタンがクリックされたら
    upper_btn.click(
        fn=to_uppercase,
        inputs=[input_text],
        outputs=[uppercase_output],
        # AIクライアントから呼び出す際のツール名を指定
        api_name="to_uppercase_tool"
    )


# 5. サーバーの起動
if __name__ == "__main__":
    # Blocksでも mcp_server=True を指定する
    demo.launch(mcp_server=True)




リスト4-7
import gradio as gr
from PIL import Image, ImageOps


# 1. 処理を行う関数（AIが利用するツールの中身）
def apply_sepia(input_image):
    """
    画像を受け取り、セピアフィルターを適用して返します。
    Args:
        input_image: アップロードされた画像 (numpy array または PIL Image)
    Returns:
        セピア色に変換された画像
    """
    if input_image is None:
        return None
    
    # PIL Image形式に変換して処理
    img = Image.fromarray(input_image)
    gray = ImageOps.grayscale(img)
    # 簡易的なセピア着色（黄色いオーバーレイをかける）
    sepia = ImageOps.colorize(gray, "#704214", "#C0C080")
    
    return sepia


# 2. Gradioインターフェースの定義
# ここで定義した inputs と outputs の情報が、そのままMCPのツール定義として使われます。
demo = gr.Interface(
    fn=apply_sepia,
    inputs=gr.Image(label="元の画像をアップロード"),
    outputs=gr.Image(label="変換後の画像"),
    title="Sepia Filter App",
    description="画像をアップロードするとセピア色に変換します。"
)


# 3. サーバーの起動 (MCPサーバー機能の有効化)
if __name__ == "__main__":
    # mcp_server=True を指定するだけで、MCPサーバーとして動作します。
    demo.launch(mcp_server=True)




リスト4-8
import gradio as gr  
import tempfile 
from PIL import Image
import numpy as np


# GradioのMCP（Model-Centric Platform）ツールとして関数をデコレート
@gr.mcp.tool(
    # AIプラットフォームがツールをどのように扱うかを定義
    _meta={
        # ツール実行の結果をHTMLリソースで表示することを指定
        "openai/outputTemplate": "ui://widget/app.html",
        # ツール結果がウィジェット（UI）を生成できることを示す
        "openai/resultCanProduceWidget": True,
        # 生成されたウィジェットがユーザーに表示可能であることを示す
        "openai/widgetAccessible": True,
    }
)
# パワーロー（ガンマ）変換を適用するメインの画像処理関数
def power_law_image(input_path: str, gamma: float = 0.5) -> str:
    """
    Applies a power-law (gamma) transformation to an image file and saves
    the result to a temporary file.


    Args:
        input_path (str): Path to the input image. (Gradioからファイルパスが渡される)
        gamma (float): Power-law exponent. <1 brightens, >1 darkens. (デフォルトは0.5で明るくする)


    Returns:
        str: Path to the saved temporary output image.
    """
    # 1. 画像の読み込みと前処理
    # 入力パスから画像を読み込み、RGB形式に変換
    img = Image.open(input_path).convert("RGB")
    # 画像データをNumPy配列に変換し、0~1の範囲に正規化
    arr = np.array(img, dtype=np.float32) / 255.0


    # 2. ガンマ補正（パワーロー変換）の適用
    # arr = arr^gamma の計算を実行（画像の明るさを調整する処理）
    arr = np.power(arr, gamma)


    # 3. 後処理と保存
    # 0-255の範囲に戻しuint8型に変換、クリッピングでオーバーフロー予防
    arr = np.clip(arr * 255, 0, 255).astype(np.uint8)
    # NumPy配列をPIL Imageオブジェクトに変換
    out_img = Image.fromarray(arr)


    # 処理結果を一時ファイルに保存
    # 一時ファイルを作成し、閉じた後も削除されないよう設定
    tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
    # 処理後の画像を一時ファイルに保存
    out_img.save(tmp_file.name)
    # ファイルハンドルを閉じる
    tmp_file.close()


    # 処理結果の画像が保存された一時ファイルのパスを返す
    return tmp_file.name


# ウィジェットとして提供するHTMLリソースを定義
@gr.mcp.resource("ui://widget/app.html", mime_type="text/html+skybridge")
def app_html():
    # ウィジェットとして表示するHTML、CSS、JavaScriptを定義
    visual = """
    <style>
        /* CSS: スタイル定義（省略） */
    </style>
    <div id="image-container">
        <img id="image-display" alt="Processed image" />
        <button id="brighten-btn">Brighten</button>
    </div>
    <script>
        const imageEl = document.getElementById('image-display');
        const btnEl = document.getElementById('brighten-btn');


        // ツール出力から画像URL（ファイルパス）を抽出するヘルパー関数
        function extractImageUrl(data) {
            // ... (URL抽出ロジック) ...
            if (data?.text?.startsWith('Image URL: ')) {
                return data.text.substring('Image URL: '.length).trim();
            }
            // ... (他のデータ構造からの抽出ロジック) ...
            return null;
        }


        // ツール出力の結果をウィジェットにレンダリングする関数
        function render() {
            // ツール出力からURLを取得
            const url = extractImageUrl(window.openai?.toolOutput);
            // URLがあれば画像のsrcを設定し、画像を表示
            if (url) imageEl.src = url;
        }


        // Brightenボタンがクリックされたときに実行される非同期関数
        async function brightenImage() {
            btnEl.disabled = true;
            btnEl.textContent = 'Brightening...';
            // GradioのAPI経由でPythonのpower_law_image関数を呼び出す
            const result = await window.openai.callTool('power_law_image', {
                input_path: imageEl.src // 現在表示されている画像のURLを渡す
            });
            // 新しい結果からURLを抽出し、画像表示を更新
            const newUrl = extractImageUrl(result);
            if (newUrl) imageEl.src = newUrl;
            btnEl.disabled = false;
            btnEl.textContent = 'Brighten';
        }


        // イベントリスナーの設定
        btnEl.addEventListener('click', brightenImage);
        // グローバル変数変更イベントを監視、ツール出力があればレンダリング
        window.addEventListener("openai:set_globals", (event) => {
            if (event.detail?.globals?.toolOutput) render();
        }, { passive: true });


        render(); // 初期ロード時に一度レンダリングを実行
    </script>
    """
    return visual


# GradioのBlocksコンテナを使ってUIを定義
with gr.Blocks() as demo:
    with gr.Row():
        with gr.Column():
            # 画像コンポーネント。type="filepath"でファイルパスを渡す
            original_image = gr.Image(label="Original Image", type="filepath")
            btn = gr.Button("Brighten Image")
        with gr.Column():
            # 処理結果を表示するコンポーネント
            output_image = gr.Image(label="Output Image", type="filepath")
            # 埋め込みHTMLコードを表示するコンポーネント（デバッグ用）
            html = gr.Code(language="html", max_lines=20)


    # ボタンクリック時のアクション定義（ローカルGradioのUI動作）
    # 1. 画像処理関数を実行し、結果をoriginal_imageに戻して表示を更新
    btn.click(power_law_image, inputs=original_image, outputs=original_image)
    # 2. app_html関数でHTMLの内容をgr.Codeコンポーネントに表示
    btn.click(app_html, outputs=html)


# サーバーの起動
if __name__ == "__main__":
    demo.launch(mcp_server=True, share=True)




リスト5-1
from google import genai # genaiをインポート
# import os # os.environ.get利用時に用意


# APIキーを用意
API_KEY= '<APIキー>'
# GEMINI_API_KEY環境変数から値を取り出す（推奨）
# os.environ.get("GEMINI_API_KEY") 


# Client（LLMとやり取りする部品）を作成
client = genai.Client(api_key=API_KEY)


# プロンプトを入力
prompt = input('prompt: ')


# Clientにプロンプトを送り、結果を受け取る
response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents=prompt,
)


# 結果から応答のテキストを表示する
print(response.text)




リスト5-2
from google import genai # 新しいSDKをインポート
import os # APIキーを環境変数から読み込む


# APIキーの設定
# セキュリティのため、環境変数から読み込むことを推奨
# 実際には os.environ.get("GEMINI_API_KEY") を使用
API_KEY = "<APIキー>"


# Client（LLMとやり取りする部品）を作成
# APIキーを client の初期化時に渡します
client = genai.Client(api_key=API_KEY)


# モデル名
MODEL_NAME = "gemini-2.5-flash" # 新しいSDKの一般的なモデル名


# 1. チャットセッション（会話の部屋）を開始する
# client.chats.create() を使用します
# history=[] とすることで、真っ白な状態から会話をスタートします
chat = client.chats.create(
    model=MODEL_NAME,
    history=[]
)


print("Geminiとのチャットを開始します。（'exit'と入力すると終了します）")
print("-" * 30)


# 2. ずっと繰り返す（無限ループ）
while True:
    # 3. ユーザーからの入力を受け取る
    user_input = input("あなた: ")


    # 'exit' と入力されたらループを抜けて終了する
    if user_input.lower() == "exit":
        print("チャットを終了します。")
        break
    
    # 入力が空っぽならスキップする
    if not user_input.strip(): # 空白文字のみの場合も考慮
        continue


    try:
        # 4. チャット機能を使ってメッセージを送る
        # chat オブジェクトの .send_message() メソッドを使います（ここは変更なし）
        response = chat.send_message(user_input)
        
        # 5. 返事を表示する
        print(f"Gemini: {response.text}")
        print("-" * 30)
        
    except Exception as e:
        # エラーが起きたら表示する（通信エラーなど）
        print(f"エラーが発生しました: {e}")




リスト5-3
import os


def list_files_and_save(output_filename="file_list.txt"):
    """
    現在のディレクトリにあるファイルとフォルダの一覧を取得し、
    指定されたテキストファイルに1行ずつ保存します。
    """
    try:
        # 現在のディレクトリのファイルとフォルダの一覧を取得
        # os.listdir() はファイルだけでなく、サブディレクトリも含む
        items = os.listdir('.') # '.' は現在のディレクトリを意味します


        # リストをソートすると、出力が見やすくなります
        items.sort()


        # テキストファイルに書き込む
        # encoding='utf-8' を指定することで、日本語などの特殊なファイル名も
        # 正しく扱えるようになります。
        with open(output_filename, 'w', encoding='utf-8') as f:
            for item in items:
                f.write(item + '\n')


        print(f"ファイルとフォルダの一覧が '{output_filename}' に保存されました。")


    except FileNotFoundError:
        print("エラー: 現在のディレクトリが見つかりません。")
    except PermissionError:
        print("エラー: ディレクトリへのアクセス権がありません。")
    except Exception as e:
        print(f"予期せぬエラーが発生しました: {e}")


# スクリプトが直接実行された場合にのみ関数を呼び出す
if __name__ == "__main__":
    list_files_and_save()




リスト5-4
from google import genai 
from google.genai import types # AIとの送受で使用するデータ型


# APIキーの設定
API_KEY= '<APIキー>'


# Clientの作成
client = genai.Client(api_key=API_KEY) 


# --- ツール（道具）の設定 ---


# Geminiに提供するツールとしてGoogle Searchを設定
grounding_tool = types.Tool(
    # Toolの定義：Google Searchを有効にする
    google_search=types.GoogleSearch() 
)


# コンテンツ生成時の設定を作成
config = types.GenerateContentConfig(
    # 設定に、grounding_toolを組み込む
    tools=[grounding_tool] 
)


# --- 実行フェーズ ---


# ユーザーからのプロンプト（質問）入力を受け付ける
prompt = input("prompt: ")


# Clientを使ってコンテンツ生成を依頼
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents=prompt,
    config=config,  # Google Searchツールを使う設定
)


# AIからの応答テキストを表示する
print(response.text)




リスト5-5
from google import genai
from google.genai import types


# APIキーの設定
API_KEY= '<AIPキー>'


# Clientの作成
client = genai.Client(api_key=API_KEY)


# プレプロンプトの用意
prompt = "ここから徒歩10分以内のおいしいレストランを10店舗挙げてください。"


# 位置情報の入力
lat = input("latitude: ")
long = input("longitude: ")


# Geminiへの送信と結果の取得
response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents=prompt,
    config=types.GenerateContentConfig(
        # Googleマップツールを追加する
        tools=[types.Tool(google_maps=types.GoogleMaps())],
        # 関連する場所のコンテキストを提供する
        tool_config=types.ToolConfig(
            retrieval_config=types.RetrievalConfig(
                lat_lng=types.LatLng(
                    latitude=float(lat),
                    longitude=float(long)
                )
            )
        ),
    ),
)


print("Generated Response:")
print(response.text)


if grounding := response.candidates[0].grounding_metadata:
  if grounding.grounding_chunks:
    print('-' * 40)
    print("Sources:")
    for chunk in grounding.grounding_chunks:
      print(f'- [{chunk.maps.title}]({chunk.maps.uri})')




リスト5-6
from google import genai
from google.genai import types


# APIキーの用意
API_KEY = '<APIキー>'


client = genai.Client(api_key=API_KEY)


# --- 設定（Config）の定義 ---


# 1. ツール（道具）の設定
# Google Maps Toolを有効にします。
maps_tool = types.Tool(
    google_maps=types.GoogleMaps()
)


# 2. コンテンツ生成設定
config = types.GenerateContentConfig(
    # tools引数にGoogle Maps Toolを設定
    tools=[maps_tool],
)


# --- 実行フェーズ ---


print("Google Maps Toolを使ってレストランを検索します。")
# ユーザーにプロンプト（地名と検索条件）を入力してもらう
prompt_prefix = "以下の条件に合う、主要なレストランの情報を、なぜそれが適切かを説明しながらリスト形式で整理してください。"
user_input = input(f"\n検索条件（例: 佐倉市周辺のランチが美味しい和食）: ")
prompt = f"{prompt_prefix} {user_input}"


print("-" * 40)
print(f"送信プロンプト: {prompt}")
print("-" * 40)


# AIにコンテンツ生成を依頼
response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents=prompt,
    config=config,
)


# --- 応答の出力 ---


print("Generated Response:")
print(response.text)


# Google Maps Toolが使用された場合、情報源を表示する
# 応答候補の最初の要素からグラウンディングメタデータを確認
if response.candidates and (grounding := response.candidates[0].grounding_metadata):
    if grounding.grounding_chunks:
        print('-' * 40)
        print("Sources (Google Maps):")
        
        # 取得されたグラウンディングチャンクをループして表示
        for chunk in grounding.grounding_chunks:
            # mapsフィールドが存在し、タイトルとURIがある場合に表示
            if chunk.maps and chunk.maps.title and chunk.maps.uri:
                print(f'- [{chunk.maps.title}]({chunk.maps.uri})')




リスト5-7
from google import genai
from google.genai import types


# APIキーの設定
API_KEY= '<APIキー>'


# Clientの作成
client = genai.Client(api_key=API_KEY)


# プロンプトで処理の内容を入力
prompt = input("prompt: ")る


# --- コード実行ツールの有効化 ---
# コンテンツ生成時の設定 (Config) を作成
config=types.GenerateContentConfig(
    # tools引数に、コード実行ツールを定義
    tools=[types.Tool(code_execution=types.ToolCodeExecution())] 
)


# Geminiへの送信と結果の取得
response = client.models.generate_content(
    model="gemini-2.5-flash",
    # ユーザーのプロンプトの前に、コード実行の指示を追加
    contents="計算用のコードを生成して実行し、得られた結果を表示します。" + prompt,
    config=config 
)


# --- 応答内容のパースと表示 ---
# 応答は複数の要素で構成されるためループ処理する
for part in response.candidates[0].content.parts:
    
    # 応答に含まれるテキストを表示
    if part.text is not None:
        print(part.text)
        
    # Geminiが生成したPythonコードを表示
    if part.executable_code is not None:
        # executable_code.codeに実行されたコード本体が入っている
        print("-" * 20 + " [実行されたコード] " + "-" * 20)
        print(part.executable_code.code)
        
    # コード実行の結果を表示
    if part.code_execution_result is not None:
        # code_execution_result.outputに実行結果が入っている
        print("-" * 20 + " [コード実行結果] " + "-" * 20)
        print(part.code_execution_result.output)




リスト6-1
from playwright import sync_api


# Playwrightを使った操作のメイン関数 (async/awaitなし)
def main():
    # sync_api.sync_playwright() を呼び出すことでセッションを開始
    with sync_api.sync_playwright() as p:
        
        # Chromiumブラウザを起動 (headless=TrueでGUI表示なし)
        # p.chromium.launch() は Playwrightオブジェクトのメソッド
        browser = p.chromium.launch(headless=True)
        
        # 新しいページ（タブ）を開く
        page = browser.new_page()
        
        # 指定したURLに移動
        print("Googleのトップページにアクセスします...")
        page.goto("https://www.google.com/")
        
        # ページのタイトルを取得して表示
        title = page.title()
        print(f"ページのタイトル: {title}")
        
        # ブラウザを閉じる
        browser.close()
        print("ブラウザを閉じました。")


# main_sync_alternative関数を実行
if __name__ == "__main__":
    main()




リスト6-2
import asyncio
from playwright.async_api import async_playwright
from bs4 import BeautifulSoup


def extract_text_content(html_string: str) -> str:
    """
    HTMLコードからタグをすべて削除しテキストのみを取得する関数


    Args:
        html_string (str): 処理したいHTMLコードの文字列


    Returns:
        str: 抽出された純粋なテキストコンテンツ
    """
    # 1. BeautifulSoupオブジェクトを作成しHTMLを解析する
    # 'html.parser' でHTMLを構造化データとして読み込む
    soup = BeautifulSoup(html_string, 'html.parser')
    
    # 2.オブジェクトから、すべてのテキストコンテンツを抽出する
    # .get_text() メソッドでタグを除去し、テキストだけを結合する
    text_content = soup.get_text()
    
    # 3. 連続する改行やスペースを整理して返す（オプション）
    # 抽出後のテキストは改行や余分なスペースがあるため整形する
    lines = (line.strip() for line in text_content.splitlines())
    chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
    text = '\n'.join(chunk for chunk in chunks if chunk)
    
    return text


# Playwrightを使った操作のための非同期関数
async def main():
    # Playwrightを起動し、ブラウザ操作のコンテキストを作成
    async with async_playwright() as p:
        # Chromiumブラウザを起動
        # 'chromium'を指定可能
        browser = await p.chromium.launch(headless=True)
        
        # 新しいページ（タブ）を開く
        page = await browser.new_page()
        
        # 指定したURLに移動
        print("Googleのトップページにアクセスします...")
        await page.goto("https://news.google.com/home?hl=ja")
        
        # ページのコンテンツを取得して表示
        html_content = await page.content()
        content = extract_text_content(html_content)
        print(f"ページのコンテンツ:")
        print(content)
        
        # ブラウザを閉じる
        await browser.close()
        print("ブラウザを閉じました。")


# main関数を実行
if __name__ == "__main__":
    asyncio.run(main())




リスト6-3
from playwright.sync_api import sync_playwright


def search_duckduckgo_ja(query: str, headless: bool = True):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=headless)
        page = browser.new_page()
        # 日本語ページ表示を優先するパラメータを付与
        page.goto("https://duckduckgo.com/?kl=jp&kt=ja")
        # 検索語を入力（日本語を直接渡す）
        page.fill("input[name='q']", query)
        # Enterで送信して確実にナビゲーション完了を待つ
        with page.expect_navigation():
            page.keyboard.press("Enter")


        # 結果取得：複数の可能性のあるセレクターを試す
        selectors = [
            "a.result__a",               # 標準の結果リンク
            ".result__title a",
            "a[data-testid='result-title-a']",
            ".result a[href^='http']"
        ]
        results = []
        for sel in selectors:
            try:
                page.wait_for_selector(sel, timeout=8000)
                for a in page.query_selector_all(sel):
                    title = a.inner_text().strip()
                    href = a.get_attribute("href")
                    if title and href:
                        results.append({"title": title, "url": href})
                if results:
                    break
            except Exception:
                continue


        browser.close()
        return results


if __name__ == "__main__":
    prompt = input("検索テキスト: ")
    items = search_duckduckgo_ja(prompt, headless=False)
    for i, r in enumerate(items[:10], 1):
        print(f"{i}. {r['title']}\n   {r['url']}")




リスト6-4
from playwright.sync_api import sync_playwright


# ---------- 基本操作（単一責任の関数） ----------
def launch_browser(p, headless: bool = True):
    """ブラウザを起動してトップページを開く。戻り値: (browser, page)"""
    browser = p.chromium.launch(headless=headless)
    page = browser.new_page()
    page.goto("https://duckduckgo.com/?kl=jp&kt=ja")
    return browser, page


def go_home(page):
    """検索トップへ戻る"""
    page.goto("https://duckduckgo.com/?kl=jp&kt=ja")


def fill_search_field(page, text: str):
    """検索入力フィールドにテキストをセット"""
    page.fill("input[name='q']", text)


def submit_search_by_enter(page):
    """Enter キーで検索を送信（ナビゲーションを待つ）"""
    with page.expect_navigation():
        page.keyboard.press("Enter")


def click_element(page, selector: str):
    """指定セレクターをクリック（存在しない場合は例外）"""
    page.click(selector)


def wait_for_any_selector(page, selectors, timeout=8000):
    """複数セレクターのうちいずれかが現れるのを待つ（最初に見つかったものを返す）"""
    for sel in selectors:
        try:
            page.wait_for_selector(sel, timeout=timeout)
            return sel
        except Exception:
            continue
    return None


# ---------- 抽出ロジック ----------
def extract_results_from_page(page, max_results: int = 10):
    """ページ上の検索結果を取得して辞書リストで返す"""
    selectors = [
        "a.result__a",
        ".result__title a",
        "a[data-testid='result-title-a']",
        ".result a[href^='http']"
    ]
    results = []
    # まずどれかのセレクターが存在するか確認
    present = wait_for_any_selector(page, selectors, timeout=8000)
    if not present:
        return results


    # 存在するセレクターで要素を列挙して抽出
    for sel in selectors:
        try:
            elements = page.query_selector_all(sel)
            for a in elements:
                title = a.inner_text().strip()
                href = a.get_attribute("href")
                if title and href:
                    results.append({"title": title, "url": href})
                if len(results) >= max_results:
                    break
            if results:
                break
        except Exception:
            continue
    return results


# ---------- 検索処理（高レベル） ----------
def perform_search(page, query: str, max_results: int = 10):
    """1回分の検索を行い結果を返す（トップへ戻してEnter送信→抽出）"""
    go_home(page)
    fill_search_field(page, query)
    submit_search_by_enter(page)
    # 最低限の待機（描画/SPA対応）
    try:
        page.wait_for_load_state("networkidle", timeout=5000)
    except Exception:
        pass
    page.wait_for_timeout(800)
    return extract_results_from_page(page, max_results=max_results)


# ---------- エージェントループ ----------
def agent_loop(headless: bool = False):
    with sync_playwright() as p:
        browser, page = launch_browser(p, headless=headless)
        print('検索を繰り返します。終了は空入力または "quit"。')
        try:
            while True:
                query = input("検索テキスト: ").strip()
                if not query or query.lower() == "quit":
                    break


                items = perform_search(page, query, max_results=10)
                if not items:
                    print("結果が見つかりませんでした。ブラウザ画面を確認してください。")
                else:
                    for i, r in enumerate(items, 1):
                        print(f"{i}. {r['title']}\n   {r['url']}")
                print("-" * 60)
        finally:
            browser.close()


if __name__ == "__main__":
    agent_loop(headless=False)




リスト6-5
```Python
from playwright.sync_api import sync_playwright
from google import genai
from google.genai import types
import re
import json


# ---------- Gemini (genai) クライアント設定 ----------
API_KEY = "<APIキー>"


def create_gemini_client():
    client = genai.Client(api_key=API_KEY)
    return client


def get_search_keywords(client: genai.Client, prompt:str):
    system_prompt = '''
以下のメッセージから、検索のためのキーワードを1~5個取り出してください。
返す値は以下のテキストにしてください。
```
キーワード1 キーワード2 キーワード3 ...
```
余計な値をつけず、上記のテキストだけを返してください。


メッセージ：
'''
    resp = client.models.generate_content(
        model="gemini-2.5-flash", # クライアントの期待するモデル識別子に合わせる
        contents=system_prompt+ prompt,        # 多くのバージョンで `input` に文字列を渡す
    )
    # 応答テキストを取得（最初の候補）
    text = resp.text if hasattr(resp, "text") else getattr(resp, "output", str(resp))
    return text
 
def parse_with_gemini(client: genai.Client, natural_input: str) -> dict:
    """
    Gemini に自然言語指示を送り、JSON でクエリ構造を返してもらう。
    期待する JSON 形式:
    { "query": "...", "max_results": 10, "headless": false }
    """
    # プロンプト設計：出力を必ず JSON にする
    system = (
        "あなたは入力から検索パラメータを抽出するパーサーです。"
        "返答は必ずJSONのみで返してください。"
        "JSON フィールド: query (string), max_results (integer, default 10), headless (boolean, default false)."
        "例: {\"query\": \"Playwright Python サンプル\", \"max_results\": 5, \"headless\": false}"
    )
    user = f"入力: \"{natural_input}\"\n抽出して JSON を返してください。"


    full_prompt = system + "\n\n" + user


    try:
        resp = client.models.generate_content(
            model="gemini-2.5-flash", # クライアントの期待するモデルに合わせる
            contents=full_prompt,        # 多くのバージョンで `input` に文字列を渡す
        )


        # 応答テキストを取得（最初の候補）
        text = resp.text if hasattr(resp, "text") else getattr(resp, "output", str(resp))
        # JSON 部分だけ取り出す（頑健に）
        json_text = extract_json_substring(text)
        parsed = json.loads(json_text)
        # 正規化 / デフォルト
        query = parsed.get("query") if parsed.get("query") else natural_input
        max_results = int(parsed.get("max_results", 10))
        headless = bool(parsed.get("headless", False))
        return {"query": query, "max_results": max_results, "headless": headless}
    except Exception:
        # フォールバック（シンプルなルールベース）
        return fallback_parse(natural_input)


def extract_json_substring(s: str) -> str:
    """文字列から最初に現れる JSON オブジェクトを取り出す（簡易実装）"""
    start = s.find("{")
    if start == -1:
        raise ValueError("JSON not found in model response")
    # 最良の簡易方法: 最初のマッチする括弧対を見つける
    depth = 0
    for i in range(start, len(s)):
        if s[i] == "{":
            depth += 1
        elif s[i] == "}":
            depth -= 1
            if depth == 0:
                return s[start:i+1]
    raise ValueError("Unterminated JSON in model response")


def fallback_parse(natural_input: str) -> dict:
    m = re.search(r'(\d+)\s*(件|個|results|entries)?', natural_input)
    max_results = int(m.group(1)) if m else 10
    headless = not bool(re.search(r'(表示|見たい|見ながら|headless=False|非ヘッドレス)', natural_input))
    qmatch = re.search(r'["“](.+?)["”]', natural_input)
    if qmatch:
        query = qmatch.group(1)
    else:
        query = re.sub(r'検索|探して|取得|教えて|見せて|ください|お願いします|件|個|results|entries', '', natural_input, flags=re.I).strip()
        if not query:
            query = natural_input.strip()
    return {"query": query, "max_results": max_results, "headless": headless}


# ---------- Playwright 操作関数 ----------
def launch_browser(p, headless: bool = True):
    browser = p.chromium.launch(headless=headless)
    page = browser.new_page()
    page.goto("https://duckduckgo.com/?kl=jp&kt=ja")
    return browser, page


def go_home(page):
    page.goto("https://duckduckgo.com/?kl=jp&kt=ja")


def fill_search_field(page, text: str):
    page.fill("input[name='q']", text)


def submit_search_by_enter(page):
    with page.expect_navigation():
        page.keyboard.press("Enter")


def wait_for_any_selector(page, selectors, timeout=8000):
    for sel in selectors:
        try:
            page.wait_for_selector(sel, timeout=timeout)
            return sel
        except Exception:
            continue
    return None


def extract_results_from_page(page, max_results: int = 10):
    selectors = [
        "a.result__a",
        ".result__title a",
        "a[data-testid='result-title-a']",
        ".result a[href^='http']"
    ]
    results = []
    present = wait_for_any_selector(page, selectors, timeout=8000)
    if not present:
        return results
    for sel in selectors:
        try:
            elements = page.query_selector_all(sel)
            for a in elements:
                title = a.inner_text().strip()
                href = a.get_attribute("href")
                # スニペットは要望があれば追加可能
                if title and href:
                    results.append({"title": title, "url": href})
                if len(results) >= max_results:
                    break
            if results:
                break
        except Exception:
            continue
    return results


def perform_search(client: genai.Client, page, query: str, max_results: int = 10):
    go_home(page)
    fill_search_field(page, str(query))
    submit_search_by_enter(page)
    try:
        page.wait_for_load_state("networkidle", timeout=5000)
    except Exception:
        pass
    page.wait_for_timeout(800)
    return extract_results_from_page(page, max_results=max_results)


# ---------- エージェントループ（Gemini 統合版） ----------
def agent_loop_with_gemini():
    client = create_gemini_client()
    with sync_playwright() as p:
        # 初期 headless は False（対話確認用）。Gemini 指示で変更するなら再起動が必要。
        browser, page = launch_browser(p, headless=False)
        print('自然言語で指示してください（例: "Playwright の日本語サンプルを10件表示して"）。終了は空入力または "quit"。')
        try:
            while True:
                natural = input("指示: ").strip()
                if not natural or natural.lower() == "quit":
                    break


                # 検索キーワードを取得
                parsed = parse_with_gemini(client, natural)
                query = parsed["query"]
                query = get_search_keywords(client, query)
                max_results = parsed["max_results"]
                


                # headless 指示が現在のブラウザと異なる場合は通知のみ
                if parsed.get("headless") is True:
                    print("注意: 指示で headless=True が要求されています。現在は表示モードで動作中です。")


                items = perform_search(client, page, query, max_results=max_results)
                if not items:
                    print("結果が見つかりませんでした。ブラウザを確認してください。")
                else:
                    print(f'検索クエリ: {query} （最大 {max_results} 件）')
                    for i, r in enumerate(items, 1):
                        print(f"{i}. {r['title']}\n   {r['url']}")
                print("-" * 60)


                # トップに戻して安定化
                page.goto("https://duckduckgo.com/?kl=jp&kt=ja")
        finally:
            browser.close()


if __name__ == "__main__":
    agent_loop_with_gemini()




リスト6-6
from typing import Any, List, Tuple
from playwright.sync_api import sync_playwright


# 画面サイズの定数（仮の値）
SCREEN_WIDTH = 1440
SCREEN_HEIGHT = 900


# Playwrightの'page'オブジェクトがブラウザ画面を操作する
def launch_browser(p, headless: bool = True):
    """ブラウザを起動してトップページを開く。戻り値: (browser, page)"""
    url = input("https://")
    browser = p.chromium.launch(headless=headless)
    page = browser.new_page()
    page.goto("https://google.co.jp")
    return browser, page


def show_google_links():
    with sync_playwright() as p:
        browser, page = launch_browser(p, headless=True)
        elements = page.query_selector_all("a")
        for a in elements:
            title = a.inner_text().strip()
            print(title)


if __name__ == "__main__":
    show_google_links()




リスト6-7
```Python
import time
import re # このコードでは未使用ですが、標準モジュールです。
from playwright.sync_api import sync_playwright # ウェブブラウザ操作のためのライブラリ
from google import genai # Gemini API クライアントライブラリ
from google.genai import types
from google.genai.types import Content, Part


# --- 設定項目 ---
# APIキーの設定
API_KEY= '<APIキー>'' # 実際のAPIキーに置き換える


# Clientの作成
client = genai.Client(api_key=API_KEY)


# 使用するComputer Use対応モデルのID
MODEL_ID = 'gemini-2.5-computer-use-preview-10-2025'
# ブラウザの画面幅（ピクセル）。座標の計算に使用。
SCREEN_WIDTH = 1280
# ブラウザの画面高さ（ピクセル）。座標の計算に使用。
SCREEN_HEIGHT = 800


# 検索したい商品の入力
TARGET_ITEM = input("調べたい商品(例:ワイヤレスイヤホン): ")


# AIモデルに与える初期指示（プロンプト）
USER_PROMPT = (
    f"amazon.co.jp にアクセスして、検索欄に '{TARGET_ITEM}' と入力し検索してください。"
    "検索結果の商品リストが表示されたら、それ以上の操作は不要です。"
    "タスク完了と報告して終了してください。"
)


# --- ヘルパー関数 (Computer Useアクション) ---


def denormalize_x(x: int, screen_width: int) -> int:
    """
    ノーマライズされたX座標 (0-1000) を、実際の画面幅に基づいて
    ピクセル単位のX座標に変換します。
    """
    return int(x / 1000 * screen_width)


def denormalize_y(y: int, screen_height: int) -> int:
    """
    ノーマライズされたY座標 (0-1000) を、実際の画面高さに基づいて
    ピクセル単位のY座標に変換します。
    """
    return int(y / 1000 * screen_height)


def execute_function_calls(candidate, page, screen_width, screen_height):
    """
    AIモデルの応答に含まれるFunction Call（click_at, type_text_atなど）
    を実行します。
    
    Args:
        candidate: AIモデルからの応答候補。
        page: PlaywrightのPageオブジェクト。
        screen_width (int): 画面幅。
        screen_height (int): 画面高さ。
        
    Returns:
        list: 実行された関数名とその結果 (エラー情報を含む) のタプルリスト。
    """
    results = []
    function_calls = []
    
    # 応答候補がない場合は処理をスキップ
    if not candidate.content or not candidate.content.parts:
        return results


    # 応答からFunction Callを抽出
    for part in candidate.content.parts:
        if part.function_call:
            function_calls.append(part.function_call)


    # 抽出されたFunction Callを順次実行
    for function_call in function_calls:
        fname = function_call.name
        args = function_call.args
        action_result = {}


        try:
            if fname == "click_at":
                # X/Y座標をデノーマライズしてクリックを実行
                actual_x = denormalize_x(args["x"], screen_width)
                actual_y = denormalize_y(args["y"], screen_height)
                page.mouse.click(actual_x, actual_y)
            elif fname == "type_text_at":
                # X/Y座標をデノーマライズ
                actual_x = denormalize_x(args["x"], screen_width)
                actual_y = denormalize_y(args["y"], screen_height)
                text = args["text"]
                press_enter = args.get("press_enter", False)
                
                # 1. クリックしてフォーカスを合わせる
                page.mouse.click(actual_x, actual_y)
                # 2. 既存のテキストを全選択・削除
                page.keyboard.press("Control+A") 
                page.keyboard.press("Backspace")
                # 3. 指定されたテキストを入力
                page.keyboard.type(text)
                # 4. 必要に応じてEnterキーを押す
                if press_enter:
                    page.keyboard.press("Enter")
            
            # ページ操作後、処理の終了を待つため待機
            time.sleep(2) 
            
        except Exception as e:
            # 実行中のエラーを捕捉し、結果に記録
            action_result = {"error": str(e)}


        # 実行結果をリストに追加
        results.append((fname, action_result))


    return results


def get_function_responses(page, results):
    """
    Function Callの実行結果と現在のスクリーンショットを
    Gemini APIの `FunctionResponse` 形式にパッケージ化します。
    
    Args:
        page: PlaywrightのPageオブジェクト。
        results (list): execute_function_callsの結果。
        
    Returns:
        list: AIモデルに送信される types.FunctionResponse のリスト。
    """
    # 現在のページのスクリーンショットをPNG形式で取得
    screenshot_bytes = page.screenshot(type="png")
    current_url = page.url
    function_responses = []
    
    for name, result in results:
        # 結果データに現在のURLを追加し、FunctionResponseを作成
        response_data = {"url": current_url}
        response_data.update(result)
        function_responses.append(
            types.FunctionResponse(
                name=name,
                response=response_data,
                # スクリーンショットをFunctionResponseに含める
                parts=[types.FunctionResponsePart(
                    inline_data=types.FunctionResponseBlob(
                        mime_type="image/png",
                        data=screenshot_bytes))]
            )
        )
    return function_responses


# --- データ抽出関数 ---


def extract_amazon_data(page):
    """
    Amazonの検索結果ページから、商品名、ブランド、
    金額などの情報を抽出します。
    """
    print("\n--- 商品情報の抽出を開始します ---")
    
    # 1. 検索結果コンテナが表示されるのを待機
    try:
        # 検索結果のコンテナセレクタの出現を最大10秒待つ
        page.wait_for_selector('div[data-component-type="s-search-result"]', timeout=10000)
    except Exception:
        print("Warning: 検索結果のロードを待機中にタイムアウトしました。")


    # 2. アイテム要素を取得
    # プライマリセレクタによるアイテムの特定
    items = page.locator('div[data-component-type="s-search-result"]').all()
    
    # プライマリセレクタで取得できなかった場合のフォールバックセレクタ
    if not items:
        items = page.locator('[data-asin]:not([data-asin=""])').all()
    
    extracted_count = 0
    results = []


    print(f"→ 抽出対象のアイテム数: {len(items)}")


    # 抽出は最大5件まで
    for item in items:
        if extracted_count >= 5:
            break
            
        title = None
        brand = "会社名/ブランド: 情報なし"
        price_text = "金額: 情報なし"
        
        # --- 1. 商品名 (Title) の抽出 ---
        try:
            # 商品名が含まれる要素を特定し、テキストを抽出
            title_el = item.locator("h2.a-size-base-plus span").first
            
            if title_el.count():
                extracted_title = title_el.inner_text().strip()
                if extracted_title:
                    title = extracted_title
            
            # タイトルが抽出できない場合はそのアイテムをスキップ
            if not title:
                continue 
                
        except Exception:
            continue


        # --- 2. 会社名/ブランド名 (Brand) の抽出 ---
        try:
            # 優先度1: 小さな文字のブランド名要素から抽出
            brand_el = item.locator('.s-result-item .a-size-small.a-color-secondary').first
            if brand_el.count():
                brand_text = brand_el.inner_text().strip()
                # 取得したテキストが妥当なブランド名の長さであることを確認
                if len(brand_text) > 1 and len(brand_text) < 50:
                    brand = brand_text
            
            # 優先度2: 別パターンの要素からブランド情報を探す
            if brand == "会社名/ブランド: 情報なし":
                 brand_text_el = item.locator("h2 ~ div.a-row span.a-size-base").first
                 if brand_text_el.count():
                     text = brand_text_el.inner_text().strip()
                     # 評価テキストなどではないことを確認
                     if text and not text.startswith("5つ星"):
                         brand = text


        except Exception:
            pass # 失敗しても処理を続行


        # --- 3. 金額 (Price) の抽出 ---
        try:
            # 優先度1: 非表示のクリーンな価格
            offscreen_price_el = item.locator(".a-price .a-offscreen").first
            if offscreen_price_el.count():
                price_text = offscreen_price_el.inner_text().strip()
            else:
                # 優先度2: 表示価格のシンボルと整数部分を結合
                price_whole_el = item.locator(".a-price-whole").first
                price_symbol_el = item.locator(".a-price-symbol").first
                
                if price_whole_el.count():
                    whole = price_whole_el.inner_text().strip()
                    symbol = price_symbol_el.inner_text().strip() if price_symbol_el.count() else ""
                    
                    price_text = f"{symbol}{whole}"
            
        except Exception:
            pass # 失敗しても処理を続行


        # --- 抽出完了 ---
        if title:
            extracted_count += 1
            results.append({
                "rank": extracted_count,
                "title": title,
                "brand": brand,
                "price": price_text,
            })


    return results


# --- メイン処理 ---
def run_agent():
    """
    Playwrightでブラウザ環境をセットアップし、Geminiモデルとの
    対話を通じてウェブ操作（検索）とデータ抽出を行うメインの
    処理フローです。
    """
    print("ブラウザを起動中...")
    # Playwrightのブラウザセッション管理
    with sync_playwright() as p:
        # Chromiumをヘッドレスモードで起動
        browser = p.chromium.launch(headless=True)
        # 画面サイズを指定したブラウザコンテキストを作成
        context = browser.new_context(viewport={"width": SCREEN_WIDTH, "height": SCREEN_HEIGHT})
        page = context.new_page()


        # Geminiモデルの設定
        config = types.GenerateContentConfig(
            # Computer Use (ブラウザ操作) ツールを有効化
            tools=[types.Tool(computer_use=types.ComputerUse(
                environment=types.Environment.ENVIRONMENT_BROWSER
            ))],
            # モデルの思考プロセスを含める設定
            thinking_config=types.ThinkingConfig(include_thoughts=True)
        )


        # 初期ページへ移動し、待機
        page.goto("https://www.amazon.co.jp/")
        time.sleep(1)
        # 初期ページのスクリーンショットを取得
        initial_screenshot = page.screenshot(type="png")
        
        # モデルへの初期プロンプトと初期スクリーンショットを設定
        contents = [
            Content(role="user", parts=[
                Part(text=USER_PROMPT),
                Part.from_bytes(data=initial_screenshot, mime_type='image/png')
            ])
        ]


        print(f"目標: {TARGET_ITEM} を Amazon で検索し、結果を表示させる。")


        # エージェントループ (最大10ターン)
        for i in range(10): 
            print(f"\n--- ターン {i+1} ---")
            
            # モデルに次の操作を尋ねる
            response = client.models.generate_content(
                model=MODEL_ID,
                contents=contents,
                config=config,
            )


            candidate = response.candidates[0]
            # モデルの応答を会話履歴に追加
            contents.append(candidate.content)


            # モデルからのテキスト応答を表示
            text_parts = [part.text for part in candidate.content.parts if part.text]
            full_text = " ".join(text_parts)
            if full_text:
                print(f"モデル: {full_text}")


            # Function Callが存在するか確認
            has_function_calls = any(part.function_call for part in candidate.content.parts)
            
            # アクションがないかタスク完了と判断したらループを終了
            if not has_function_calls or "完了" in full_text or "終了" in full_text:
                print("検索フェーズ完了。データ抽出へ移行します。")
                break


            # Function Callを実行
            results = execute_function_calls(candidate, page, SCREEN_WIDTH, SCREEN_HEIGHT)
            # 実行結果とスクリーンショットをモデルへ返す応答を準備
            function_responses = get_function_responses(page, results)
            
            # Function Responseを会話履歴に追加する
            contents.append(
                Content(role="user", parts=[Part(function_response=fr) for fr in function_responses])
            )


        # ループ終了後、最終的なデータ抽出を実行
        data_list = extract_amazon_data(page)
        
        # 抽出結果の表示
        print("\n" + "="*50)
        print(f"【抽出結果: '{TARGET_ITEM}'】")
        if data_list:
            for item in data_list:
                print(f"No.{item['rank']}")
                print(f"商品名: {item['title']}")
                print(f"会社名: {item['brand']}")
                print(f"金額  : {item['price']}")
                print("-" * 50)
        else:
            print("データを抽出できませんでした。Amazonのページ構造が再度変更された可能性があります。")


        print("\n終了します...")
        time.sleep(5)
        browser.close() # ブラウザセッションを終了


if __name__ == "__main__":
    # スクリプトのエントリーポイント
    run_agent()