金曜日, 12月 19, 2025
No menu items!
ホーム ブログ ページ 5573

和田アキ子が絶賛の「本物の歌唱力」グループとは?

🔸 ニュース:
歌手の和田アキ子(75)は、9日に放送されたBSフジの「ハートフルTV『虫の知らせ』」に出演し、「本当に歌が上手い」と感じるグループについて語りました。

## 和田アキ子が称賛するグループ

和田は、5人組のダンス&ボーカルグループ「Da-iCE」と「FUNKEYS」とのコラボレーションについて触れ、特に「Da-iCE」についての感想を述べました。彼女は「MUSIC FAIR」という番組で彼らと一緒にパフォーマンスをした際に、「マジで歌がうまい」と感じたと明かしました。「普段は若い子たちの番組をあまり見ないけれど、Da-iCEのパフォーマンスを見てみたら、本当にちゃんと歌っていた」とのことです。

一般的には、グループのメンバーが自分のソロパートだけ歌い、残りは録音された音声を使用することが多い中、Da-iCEはそのパフォーマンスにおいて本物の歌唱を披露したことが印象的だったようです。

## コラボ時の技術と緊張

「FUNKEYS」とのコラボレーションでは、曲の途中でテンポが変わることがあり、和田はその際にボーカリストの目を見て合図を待って歌うようにしていたと話しました。特に、瞬きが合図となっていたことを示し、「それがなかったら歌えない」と冗談めかして語りました。

さらに、彼女は20年ぶりにテレビ朝日の「ミュージックステーション」に出演した際のエピソードも披露しました。リハーサルでは完璧だったものの、本番ではあまりの緊張から最後の決めポーズが小さく可愛くなってしまったとのことです。

このように、和田アキ子は自身の経験を通じて、コラボレーションの楽しさや緊張感を共有し、視聴者に特別な瞬間を提供しました。

🧠 編集部の見解:
和田アキ子さんが「マジで歌がうまい」と評価した「Da-iCE」と「FUNKEYS」の話、とても興味深いですね!彼女が若いグループに目を向けること自体、世代を超えた音楽の受け入れを示していて、ファンとしては嬉しい限りです。

和田さんのコメントが印象的なのは、彼女自身の経験から来ているものですよね。特に「Da-iCE」のパフォーマンスが生歌であることに感銘を受けたという部分。今の音楽シーンでは、技術的な進化によって、多くのグループがパフォーマンスに重点を置きながらも、録音音源に依存することが少なくない中で、彼らが本物の歌唱力を持っているというのは貴重です。

一方で、和田さんの苦労話も共感できます。特に「FUNKEYS」とのコラボの際に、瞬きで合図を受けるというエピソードは、彼女の柔軟性と対応力を表しています。音楽は、視覚と聴覚が絶妙に絡み合うアートであり、こういったコミュニケーションが必要になる現場の緊張感も伝わってきます。

また、和田さん自身が「ミュージックステーション」に20年ぶりに出演した際のエピソードも、再び舞台に立つということの大変さを感じさせます。そして、その中で可愛らしいポーズが生まれたというのも、彼女の人間らしさが出ていて素敵ですね。緊張している中でも、自分を表現することを楽しもうとする姿勢が印象的です。

豆知識ですが、和田アキ子さんは日本の音楽シーンにおける重要な存在で、彼女のデビューは1970年代という非常に長いキャリアがあります。音楽だけでなく、バラエティ番組でも活躍し続けている彼女の影響力は計り知れません。こういった発言が若いアーティストたちにとっての励みになることでしょう。

和田さんのように、音楽に対して真剣な姿勢を持ちながらもエンターテイメントを楽しむ姿勢が、今後の音楽シーンにも良い影響を与えていくことを期待したいですね!

  • キーワード: 和田アキ子


Da-iCE をAmazonで探す
FUNKEYS をAmazonで探す
ミュージックステーション をAmazonで探す


Views: 0

「あんぱん」俳優の涙告白が話題!

🔸 ニュース:
俳優の中沢元紀が、NHKの朝の連続テレビ小説「あんぱん」に登場する弟・千尋役で注目を集めています。彼は8日に自身のSNSを更新し、6月13日放送の「あさイチ」プレミアムトークに出演することを発表しました。その発表に対し、ファンからは「いやです」や「悲しい」といった複雑な声が寄せられています。

中沢は、9日から始まる第11週について「戦争パートに入るため、これまでの爽やかな雰囲気から一変する」とコメント。その中で、視聴者に向けて「しっかりと目に焼き付けてほしい」と呼びかけました。また、彼は「プレミアムトークには緊張しているが、応援をよろしく!」と意気込みを語っています。

6日の次週予告では、千尋が真っ白な海軍の制服で「のぶさんや子供たちを守るため、立派に戦う」と語るシーンが放送され、視聴者の関心をさらに引きました。中沢の投稿には、「プレミアムトークゲスト…いやですー」「今から泣きそうだけど楽しみにしてる」といったコメントが相次ぎ、ファンの心情を映し出しています。

このように、多くのファンが作品の展開に胸を痛めながらも、中沢の活躍を楽しみにする姿が伺えます。彼が演じる千尋の運命がどのように描かれるのか、今後の展開が待ち遠しいです。

🧠 編集部の見解:
この記事は、NHKの連続テレビ小説「あんぱん」に出演する俳優・中沢元紀さんの更新に関するものですね。彼が次週からの物語の転換点、つまり「戦争パート」に入ることを予告し、さらに「プレミアムトーク」への出演を報告したことでファンからは複雑な感情が寄せられているようです。

### 感想
中沢さんの反響を見ると、彼のキャラクターに対する愛着がとても強いことが伝わってきます。視聴者が感情的になる理由も分かりますよね。戦争というテーマ自体が重く、物語が一変することへの不安も感じさせます。多くのファンが「嬉しいけど悲しい」と感じるのは、人間の感情がとても複雑であることを表していますね。

### 背景
「あんぱん」は日本の社会問題、戦争や震災をテーマにしながらも、温かさや希望を描く作品が多いのが特徴です。特に「戦争パート」に入ることで、視聴者もまた歴史的な背景を学ぶ良い機会となるでしょう。戦争が与えた影響や人々の心の葛藤を描くことで、未来への警鐘を鳴らす意義もあります。

### 豆知識
実は、朝ドラは視聴率が高いだけでなく、多くの社会的メッセージを流す役割も担っています。日本人にとっての文化的な触媒として機能しており、視聴者が共感しつつ、感情の整理をするための大切なプラットフォームなのです。このような物語を通じて、視聴者は過去を振り返りつつ、未来に向けた考えを深めることができます。

中沢さんの今後の活動と「あんぱん」のストーリー展開がどのように進んでいくのか、これからの展開が楽しみですね!

  • キーワード:プレミアムトーク


海軍の制服 をAmazonで探す

プレミアムトーク をAmazonで探す

連続テレビ小説 をAmazonで探す


※以下、出典元
▶ 元記事を読む

Views: 0

今話題のアーモンドチョコレート、あなたは何故買わない?

頭はっぴーちゃん❤︎↝@yume_nasisan

みんなのコメント

メニューを開く

十王星奈のぬいぐるみいらないのでアーモンドチョコレートだけください ↑ここでもうダメだった……www

朝霧だった何か@MR_BEST114514

メニューを開く

アーモンドチョコレートを何故スーパーで買わないのか…

アレセイア@チキン

メニューを開く

なんとかして値段釣り上げたい気持ちが見える…

常日頃本棚@Tunenihonnmono

🧠 編集部の見解:

このX(旧Twitter)ポストについて分析してみましょう。

投稿内容

ポストの内容は「流石に面白すぎる」というコメントと一緒に、画像が共有されています。具体的な内容は不明ですが、画像やコメントからはユーモアや共感を誘う要素が強いと感じられます。

人気の理由

  1. ユーモア: ユーモラスな要素が絡むことで、多くの人々が共感しやすく、シェアしたくなる内容です。
  2. 画像のインパクト: 添付された画像が視覚的にインパクトを与えることで、注目を集めやすいです。
  3. シンプルな表現: 短くて直球な表現が、見た人の心に残りやすいです。

社会的影響と関連事例

  • バイラル現象: ソーシャルメディアにおけるバイラル投稿の一例として、コロナ禍以降、簡単に拡散されるコンテンツが多く見られます。この投稿も、共有やリツイートが簡単で、他のユーザーの反応を促す要因となります。
  • 共感の文化: ネット上での共感文化の発展を示す事例でもあります。多くの人々が似たような経験や感情を持っていると感じることで、心の距離が縮まり、同じ内容をシェアすることに対して抵抗が厳しくなっています。

感想 + 背景や豆知識

この種の投稿は、特に日本のネット文化において非常に多く見られます。例えば、某有名マンガのセリフに「流石」という言葉が使われていることからも分かるように、ユーモアのセンスが日本独特のもので、多くの人に受け入れられやすい要素があります。

他にも、バイラル現象としては「猫の画像」や「人気のミーム」などがあり、これらも日々進化を遂げています。ポストがどのように拡散され、どのような形で人々の心をつかむのか、今後も観察していきたいトピックの一つです。


十王星奈のぬいぐるみ をAmazonで探す アーモンドチョコレート をAmazonで探す スーパー をAmazonで探す

📣 話題のX(旧Twitter)投稿

https://x.com/i/web/status/1931706829677175176

Views: 0

渡邊渚が語る「性暴力問題」の真実とは?

🔸 ニュース:
元フジテレビアナウンサーの渡邊渚さんが、9日に自身のインスタグラムで新たな投稿を行い、「性暴力問題」に関する手記を発表することを告知しました。

渡邊さんは、NEWSポストセブンに対してこの手記を【前編】と【後編】の2部構成で寄稿しています。前編では、2022年にNHKが実施した性被害に関するアンケートを基に、被害による心の傷を一般論としてまとめています。これにより、性暴力の問題をより広く理解するための情報が提供されています。

後編では、ハーバード大学の名誉教授で精神科医のジュディス・L・ハーマンの著書『真実と修復』からの引用が含まれており、「事実を否認することが加害者の第一の手段である」といった重要な視点が紹介されています。この部分では、性暴力問題の根本的な要因について考察がなされています。

渡邊さんは2020年にフジテレビに入社し、その後2023年7月に体調を崩し入院。2024年8月には退社し、PTSDを患っていることを公表しました。現在は、自身の経験をもとに病気との向き合い方や多様なメッセージを発信しており、1月末にはフォトエッセー『透明を満たす』を出版しています。彼女の活動は、性暴力という深刻な問題に対する意識を高めるだけでなく、精神的な健康についても多くの人々に考えるきっかけを与えています。

🧠 編集部の見解:
渡邊渚さんが「性暴力問題」について手記を寄稿したことには大きな意義を感じます。近年、この問題は社会的に注目され始めており、彼女のような公の場で活躍する人が声を上げることは、被害者の勇気を後押しする重要な一歩だと考えます。

彼女の手記は、ただの個人的な体験を語るものではなく、NHKの性被害アンケートを引用することで、広く一般論としての見解を提示しています。これによって、問題が個人のものだけでなく、社会全体に存在する重要な課題であることを強調しています。特にジュディス・L・ハーマンの言葉を用いた後編では、否認することの危険性を指摘し、加害者の立場からの視点を提供している点が印象的です。

背景として、近年の#MeToo運動などもあり、性暴力問題に対する認知が急速に変化しています。多くの人々が自らの体験を共有し、社会的な変革を求める声が高まっています。渡邊さんのような影響力のある人物の発言は、この流れを加速させる可能性があります。

豆知識として、性暴力の問題は国や文化によって認識のされ方が異なりますが、日本でも最近ようやく具体的な法整備が進みつつあります。特に、2020年には性犯罪厳罰化が議論されるなど、社会全体での意識改革が求められている時期です。

渡邊さんの手記は、多くの人にとって心の支えになり、またこの重要な議論をさらに広げていくきっかけになることを願っています。

  • キーワード: 性暴力問題


性暴力問題手記 をAmazonで探す

真実と修復 をAmazonで探す

透明を満たす をAmazonで探す


※以下、出典元
▶ 元記事を読む

Views: 0

「GABAで寝姿勢が変わる!?」

🧠 編集部の見解:

このX(旧Twitter)のポストは、投稿者が寝ている間に「GABA」という商品を枕元に置いていて、その後に「真っ平ら」になってしまったというユーモラスな体験をシェアしています。

人気の理由

  1. ユーモア: 日常の小さな出来事をコミカルに表現することで、多くの人が共感しやすくなっています。特に「真っ平ら」という表現が笑いを誘います。

  2. 親しみやすさ: GABAはストレス緩和やリラックス効果があるとされる成分で、多くの人が知っているため、話題の共有がしやすいです。

  3. 視覚的要素: 画像が添付されていることで、視覚的なインパクトが増し、投稿が目を引きます。

社会的影響

このような投稿は、ストレスを抱える現代人に向けて、ちょっとした癒しや笑いを提供します。また、GABAのような健康関連商品の認知度向上や、リラックスの重要性についてのリマインダーとしても機能します。

関連事例

近年、SNS上では「日常のあるある」シリーズが人気を博しています。特に、少しだけ変わった出来事や失敗談を共有することで、共感を生み出し、「でも、私だけじゃないんだ」と感じることができます。

豆知識

GABA(γ-アミノ酪酸)は、脳内の神経伝達物質で、リラックスを促進する作用があります。現代では、食品やサプリメントとして多くの人に親しまれていますが、実際の効果は個人差があります。眠る時に圧迫されるという日常的な出来事が、ユーモラスに表現されることで、多くの人が心に留めやすくなっています。

このように、日常の小さな失敗や経験を笑いに変えることで、コミュニティの絆を強め、ストレス解消につながる瞬間を作り出す投稿が人気を集めています。


GABA をAmazonで探す 枕 をAmazonで探す 寝具 をAmazonで探す

📣 話題のX(旧Twitter)投稿

https://x.com/i/web/status/1932050597706137846

Views: 0

立花孝志、尼崎市議選で宣言!「福井落選なら政治引退」

🔸 ニュース:
政治団体「NHK党」の立花孝志党首(57歳)が、8日に公式X(旧ツイッター)を更新し、兵庫県尼崎市の市議選に関する重要な宣言を行いました。この選挙は彼の敏感な立場を示すもので、注目を集めています。

立花氏は、「福井かんきに投票よろしくお願い致します。福井かんきが落選したら、立花孝志は、政治家引退します!」と述べ、同党の候補者である福井氏が落選すれば自身も政界を引退することを明言しました。この発言は、彼自身の政治的未来を懸けた強い決意を示しています。

さらに、9日には自身のYouTubeチャンネルでも再度福井氏について言及し、「もし彼が落選した場合、その時点で政治家を引退し、参議院選挙に立候補しません」と明言しました。これは、福井氏が当選しなければ立花氏の政治人生に大きな影響を及ぼすという意味を持っています。

また、福井氏が当選した場合についても、立花氏は「来月行われる参議院選挙でNHK党が国政政党に復帰できない場合、かつ自分が兵庫県選挙区で落選した場合も、政治家を辞める」と言明しました。このような発言は、彼の政治的従属が非常に脆弱であることを示す一方、党の将来に対する考えを反映しています。

立花氏の言葉は、政治家としての責任感と同時に、選挙の結果が持つ重大さを強調しています。読者にとって、彼の今後の動向も注目されるところです。

🧠 編集部の見解:
立花孝志氏の最近の発言、特に「福井かんき候補が落選したら、政治家引退」との宣言には、色々な感情が渦巻いていますね。政治家が自らの運命を選挙の結果にかけるというスタイルは、賛否が分かれそうですが、彼の発言は一種の「賭け」でもあります。政治の世界では、こうした覚悟の表明が時に強い支持を集めることもありますよね。

立花氏のように、政治家が自分のキャリアと選挙結果を直結させるケースは珍しく、社会的影響も大きいです。支持者にとっては、候補者への力強い期待や信頼の表明と受け取られるかもしれませんが、逆にプレッシャーになってしまう可能性も秘めています。

背景として、立花氏は元々「NHK党」という名称で活動しており、「NHK改革」を掲げた斬新なスタイルの政党です。しかし、彼の出馬表明や選挙戦に関する一連の発言は、メディアやSNSで大きな反響を呼び、マスコミからの注目も集めています。実際、彼自身もYouTubeでの発信を強化し、個人のブランドを一層高めています。

豆知識として、立花氏が代表を務めるNHK党は、政治理念以外にも多彩なアプローチで知られており、例えば彼自身が大のアニメファンであることも影響しているでしょう。このようなユニークなキャラクターと公約によって、特に若年層からの支持を得ることができるのかもしれません。

今後の選挙結果がどうなるか、興味深く見守りたいですね。立花氏の決断が政治界に与える影響にも期待が高まります。

  • キーワード: 引退


NHK党 をAmazonで探す

立花孝志 をAmazonで探す

福井かんき をAmazonで探す


※以下、出典元
▶ 元記事を読む

Views: 3

【CI/CD】GitHub Actions + AWS S3 + CloudFrontで静的サイトのCI/CD環境を構築してみた #初心者



【CI/CD】GitHub Actions + AWS S3 + CloudFrontで静的サイトのCI/CD環境を構築してみた #初心者

以前、デジタル名刺用のプロフィールページをAWS S3(htmlファイル) + CloudFrontで作成しました。(htmlファイルはGitHubで管理しています)

運用していく中で、htmlファイルを更新する度にS3のファイルを更新する(+ CloudFrontのキャッシュ削除)のが手間に感じたので、GitHub Actionsを使って自動デプロイ環境を構築してみました。本記事では、developブランチにpushするだけで、自動的にS3上のファイルが更新され、CloudFrontのキャッシュも無効化される環境をどのように構築したのかを解説します。

構成図

profile_domain_cicd.png

Route53とACMについては以下の記事で解説していますので、そちらをご覧ください。

S3とCloudFrontについては以下の記事で解説していますので、そちらをご覧ください。

後述しますが、本手順で作成されるCI/CD環境はベストプラクティスに則しておりません。その点ご注意ください。(GitHub Actionsを試してみることに重きを置いています)

  • S3バケットで静的ウェブサイトホスティングを設定済み
  • CloudFrontディストリビューションを設定済み
  • GitHubリポジトリでHTMLファイルを管理している

1. IAMポリシーの設定

以下の必要最小限の権限を持ったポリシーを設定します。

  • 該当S3バケットへのファイルの格納
  • 該当CloudFrontディストリビューションのキャッシュ削除

IAMポリシー作成手順

  1. IAMコンソールで「ポリシー」→「ポリシーを作成」
  2. ポリシーエディタの「JSON」タブを選択
  3. 以下のJSONを入力し、「次へ」を選択
  4. ポリシー名を設定し、「このポリシーで定義されている許可」に問題がなければ「ポリシーの作成」を選択

ポリシー内容

deploy-to-s3

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudfront:CreateInvalidation"
            ],
            "Resource": "arn:aws:cloudfront::YOUR-ACCOUNT-ID:distribution/YOUR-DISTRIBUTION-ID"
        }
    ]
}

注意:以下は実際の値に置き換えてください。

  • YOUR-BUCKET-NAME
  • YOUR-ACCOUNT-ID
  • YOUR-DISTRIBUTION-ID

2. AWS IAMユーザーの作成

GitHub Actionsから使用する専用のIAMユーザーを作成します。

  1. AWS IAMコンソールにログイン
  2. 「ユーザー」→「ユーザーを作成」を選択
  3. ユーザー名:github-actions-for-profile-website(任意)を入力
  4. 「次へ」を選択
  5. 「ポリシーを直接アタッチする」を選択
  6. 先ほど作成したポリシーを選択
  7. 「次へ」→「ユーザーの作成」を選択

3. 作成したIAMユーザーのアクセスキー取得

  1. 作成されたユーザーを選択し、「セキュリティ認証情報」タブを開く
  2. 「アクセスキーを作成」を選択
  3. 「その他」を選択し、「次へ」を選択
  4. アクセスキーとシークレットアクセスキーを安全な場所に保存
    ※この画面を閉じると再度確認できません

取得したアクセスキーは「4. GitHub Secretsの設定」で使用します

4. GitHub Secretsの設定

リポジトリに認証情報を安全に保存します。

  1. GitHubリポジトリの「Settings」タブを選択
  2. 「Secrets and variables」→「Actions」を選択
  3. 「New repository secret」で以下を追加
Name Value
AWS_ACCESS_KEY_ID IAMユーザーのアクセスキー
AWS_REGION リージョン(例:ap-northeast-1)
AWS_SECRET_ACCESS_KEY IAMユーザーのシークレットキー
CLOUDFRONT_DISTRIBUTION_ID CloudFrontディストリビューションID
S3_BUCKET_NAME S3バケット名

設定後、以下の画像のようになっていればOKです。

image.png

5. GitHub Actionsワークフローの作成

.github/workflows/deploy-to-s3.ymlを作成します。

deploy-to-s3.yml

name: Deploy to S3

# developブランチへのpush時に実行
on:
  push:
    branches:
      - develop

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      # リポジトリのコードをチェックアウト
      - name: Checkout code
        uses: actions/checkout@v4
      
      # AWS CLIの認証設定
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}
      
      # S3へファイルをアップロード
      - name: Upload to S3
        run: |
          aws s3 cp YOUR-HTML-FILE-PATH s3://${{ secrets.S3_BUCKET_NAME }}/YOUR-HTML-FILE-NAME \
            --content-type "text/html; charset=utf-8"
      
      # CloudFrontのキャッシュ無効化
      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/YOUR-HTML-FILE-NAME"

以上でCI/CD環境を構築することができますが、ここにたどり着くまでに何度か失敗がありました。

image.png

本項では、私が経験した失敗をいくつか共有します。

1. S3 PutObject権限エラー

An error occurred (AccessDenied) when calling the PutObject operation

原因

IAMユーザーにS3への書き込み権限がない

解決方法

  • IAMポリシーにs3:PutObject権限が含まれているか確認
  • リソースARNのバケット名が正しいか確認(私はこれが原因でした…
  • S3バケットポリシーでアクセスがブロックされていないか確認

2. CloudFront CreateInvalidation権限エラー

An error occurred (AccessDenied) when calling the CreateInvalidation operation

原因

IAMユーザーにCloudFrontの無効化権限がない

解決方法

  • IAMポリシーにcloudfront:CreateInvalidation権限を追加
  • リソースARNのディストリビューションIDが正しいか確認(私はこれが原因でした…
  • アカウントIDが正しいか確認

3. ファイルパスの誤り

リポジトリ内のディレクトリ構造に応じてパスを調整

# ルートディレクトリの場合
aws s3 cp YOUR-HTML-FILE-NAME s3://...

# サブディレクトリの場合
aws s3 cp YOUR-HTML-FILE-PATH s3://...

1. 最小権限の原則

  • 必要最小限の権限のみを付与
  • 特定のバケット、特定のディストリビューションのみにアクセスを制限

2. Secretsの管理

  • 認証情報は必ずGitHub Secretsを使用
  • コードに直接記載しない

本記事で紹介した方法は、IAMユーザーのアクセスキーを使用する従来の方法です。これは動作確認が簡単で導入しやすい一方で、セキュリティのベストプラクティスではありません。

※より安全な方法として、以下の実装が推奨されます

  • GitHub ActionsのOIDC(OpenID Connect)プロバイダーを使用
  • IAMロールとAssumeRoleによる一時的な認証情報の取得
  • 長期的なアクセスキーの保存を回避

今後、本記事の続編として、OIDCとAssumeRoleを使用したベストプラクティス版の実装に挑戦したいと考えています。

やったこと

GitHub ActionsとAWS S3 + CloudFrontを組み合わせ、CI/CD環境を構築

できるようになったこと

以下のワークフローの自動化

  1. developブランチへのpush
  2. S3へのファイルアップロード
  3. CloudFrontキャッシュの無効化

今回はいちいち手作業で実施していた作業をGitHub Actionsで実現しました。前述の通り、セキュリティ的観点からはまだ不足している部分もありますが、GitHub Actionsを実際に使ってみることができたのはよかったと思います。触ってみると簡単に実現できてかなり身近に感じられるようになった気がします。(IAM周りの設定は大変でしたが…)
近いうちにベストプラクティスに準拠した形にしたいと思います。
ありがとうございました。





Source link

Views: 0

【A2A でリモートエージェントを活用】Agent Engine と A2A サーバーを組み合わせたマルチエージェント連携入門


はじめに

下記の記事では、ADK のサブエージェント機能を利用したアーキテクチャーを紹介しましたが、この中で「サブエージェント」と「Agent as a Tool」の違いを比較しました。

まず、ADK のサブエージェントを用いると、次のように、会話の流れに応じてユーザーと会話するエージェントが自動的に切り替わります。ここでは、テラスガイドがメインのエージェント(ルートエージェント)で、とばりちゃんがサブエージェントになります。


サブエージェントを用いた会話の例

一方、本来はエージェントである「とばりちゃん」をあえて、ルートエージェントに対するツールとして登録することもできます。これが「Agent as a Tool」と呼ばれる構成です。この場合、ルートエージェントであるテラスガイドは、次のように、必要に応じてとばりちゃんから情報を取得してユーザーに提供します。


Agent as a Tool を用いた会話の例

このように、ADK には、複数のエージェントをさまざまな形で連携できる柔軟性があります。ただし、これらの構成では、ルートエージェントとサブエージェントは単一のオブジェクトにまとめられており、必ず、同一の実行環境にデプロイされる形になります。

それでは、ルートエージェントの「テラスガイド」とサブエージェントの「とばりちゃん」を別々の実行環境にデプロイした場合、これらは、どのように連携できるでしょうか? ここでは、とばりちゃんを単独のエージェントとして Agent Engine にデプロイしておき、これをテラスガイドから利用する構成を紹介します。

リモートエージェントをツールとして利用

異なる実行環境で稼働するエージェント(リモートエージェント)をネットワーク経由で利用する場合、このエージェントをツールとして利用するのが基本となります。つまり、前述の「Agent as a Tool」としての利用方法です。具体的には、リモートエージェントにネットワーク経由で質問を投げて、回答を取得する関数を作成して、これをルートエージェントに対するツールとして登録します。

わかってしまえば何でもない仕組みですが、ここでは、ツールとして登録する関数(ツール関数)がリモートエージェントにアクセスする方法として、次の3つのパターンを紹介します。

  1. Vertex AI SDK を利用して、Agent Engine 上のエージェントに直接リクエストを送信する。

  2. Agent Engine 上のエージェントにアクセスするためのプロキシーサーバーを用意して、プロキシーサーバー経由でリクエストを送信する。

  3. Agent Engine 上のエージェントにアクセスするための A2A サーバーを用意して、A2A サーバー経由でリクエストを送信する。

素朴に考えれば、1 の方法がシンプルでよいのですが、大人の事情で 2 や 3 が必要になる場合もあります。ここからは、それぞれのパターンを具体的に実装しながら、「大人の事情」について考えていきます。

環境準備

Vertex AI workbench のノートブック上で実装しながら説明するために、まずは、ノートブックの実行環境を用意しましょう。新しいプロジェクトを作成したら、Cloud Shell のコマンド端末を開いて、必要な API を有効化します。

gcloud services enable \
  aiplatform.googleapis.com \
  notebooks.googleapis.com \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  cloudresourcemanager.googleapis.com

続いて、Workbench のインスタンスを作成します。

PROJECT_ID=$(gcloud config list --format 'value(core.project)')
gcloud workbench instances create agent-development \
  --project=$PROJECT_ID \
  --location=us-central1-a \
  --machine-type=e2-standard-2

クラウドコンソールのナビゲーションメニューから「Vertex AI」→「Workbench」を選択すると、作成したインスタンス agent-develpment があります。インスタンスの起動が完了するのを待って、「JUPYTERLAB を開く」をクリックしたら、「Python 3(ipykernel)」の新規ノートブックを作成します。

この後は、ノートブックのセルでコードを実行していきます。

まず、Agent Development Kit (ADK) と Vertex AI SDK、および A2A SDK のパッケージをインストールします。

%pip install --upgrade --user \
    google-adk==1.2.1 \
    google-cloud-aiplatform==1.96.0 \
    a2a-sdk==0.2.5

インストール時に表示される ERROR: pip's dependency resolver does not currently take into... というエラーは無視してください。

インストールしたパッケージを利用可能にするために、次のコマンドでカーネルを再起動します。

import IPython
app = IPython.Application.instance()
_ = app.kernel.do_shutdown(True)

再起動を確認するポップアップが表示されるので [Ok] をクリックします。

続いて、この後で使用するモジュールをインポートして、Vertex AI の環境を初期化します。

import copy, json, os, pprint, uuid
import vertexai
from vertexai import agent_engines
from google.genai.types import Part, Content
from google.adk.agents.llm_agent import LlmAgent
from google.adk.artifacts import InMemoryArtifactService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService

[PROJECT_ID] = !gcloud config list --format 'value(core.project)'
[PROJECT_NUMBER] = !gcloud projects describe {PROJECT_ID} --format="value(projectNumber)"
LOCATION = 'us-central1'

vertexai.init(
    project=PROJECT_ID,
    location=LOCATION,
    staging_bucket=f'gs://{PROJECT_ID}'
)

os.environ['GOOGLE_CLOUD_PROJECT'] = PROJECT_ID
os.environ['GOOGLE_CLOUD_LOCATION'] = LOCATION
os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = 'True'

また、エージェントの動作をローカルで確認するための簡易的なチャットアプリのクラスを用意します。

class LocalApp:
    def __init__(self, agent, user_id='default_user'):
        self._agent = agent
        self._user_id = user_id
        self._runner = Runner(
            app_name=self._agent.name,
            agent=self._agent,
            artifact_service=InMemoryArtifactService(),
            session_service=InMemorySessionService(),
            memory_service=InMemoryMemoryService(),
        )
        self._session = None
        
    async def stream(self, query):
        if not self._session:
            self._session = await self._runner.session_service.create_session(
                app_name=self._agent.name,
                user_id=self._user_id,
                session_id=uuid.uuid4().hex,
            )
        content = Content(role='user', parts=[Part.from_text(text=query)])
        async_events = self._runner.run_async(
            user_id=self._user_id,
            session_id=self._session.id,
            new_message=content,
        )
        result = []
        async for event in async_events:
            if (event.content and event.content.parts):
                response = '\n'.join([p.text for p in event.content.parts if p.text])
                if response:
                    print(response)
                    result.append(response)
        return result

とばりちゃんエージェントのデプロイ

とばりちゃんエージェントを作成して、Agent Engine にデプロイします。

はじめに、この後で作成するテラスガイドが参照する情報と、とばりちゃんエージェントが参照する情報を定義します。

shopping_mall_info = '''
* 立地と外観:
  - 新宿駅南口から徒歩5分。賑やかな駅周辺から少し離れ、落ち着いた雰囲気のエリアに位置しています。
  - 緑豊かなオープンテラスが特徴的で、都会の中にありながらも自然を感じられる空間を提供しています。
  - 夜になると、間接照明が灯り、ロマンチックな雰囲気に包まれます。

* イベント:
  - 週末には、ジャズライブやアコースティックライブなどの音楽イベントがテラスで開催され、夜の雰囲気を盛り上げます。
  - 季節ごとのイルミネーションが美しく、訪れる人の目を楽しませます。
  - 地域住民向けのワークショップやマルシェなども開催され、地域との交流を深めています。

* テナント:
  - 個性的なセレクトショップ: 大手チェーン店だけでなく、オーナーのこだわりが詰まった隠れ家のようなセレクトショップが点在しています。
  - こだわりのレストランやカフェ: 「夜の帳」のように、落ち着いた雰囲気で質の高い食事や飲み物を楽しめるお店が集まっています。テラス席があるお店も多く、開放的な空間で食事を楽しめます。
  - 上質なライフスタイル雑貨店: 日常を豊かにする、デザイン性の高い雑貨や家具、オーガニックコスメなどを扱うお店があります。
  - アートギャラリーやミニシアター: 感性を刺激するアートや映画に触れることができるスペースがあります。
'''

coffee_shop_info = '''
* 店名: 夜の帳(よるのとばり)

* コンセプト: 一日の終わりに、静かに心と体を休ませる隠れ家のような喫茶店。落ち着いた照明と、心地よい音楽が流れる空間で、こだわりのコーヒーや軽食、デザートを提供します。

* 立地と外観:
  - 新宿スターライトテラス内の、メインフロアから少し奥まった静かな一角。3階の吹き抜けに面した見晴らしの良い場所
  - オレンジや琥珀色の暖色系間接照明が、店内から優しく漏れる。控えめな光で照らされた、筆記体のような上品な看板。

* メニュー:
  ** こだわりの珈琲:
    - 夜の帳ブレンド: 深煎りでコクがあり、ほんのりビターな大人の味わい。疲れた心に染み渡ります。
    - 月光の浅煎り: フルーティーな香りが特徴の、すっきりとした味わい。リフレッシュしたい時に。
    - カフェ・オ・レ: 丁寧に淹れたブレンドコーヒーと、温かいミルクの優しいハーモニー。
    - 水出し珈琲: じっくりと時間をかけて抽出した、まろやかで雑味のないアイスコーヒー。

  ** 軽食:
    - 厚切りトーストのたまごサンド: ふわふわの厚切りトーストに、自家製マヨネーズで和えた卵サラダをたっぷり挟みました。
    - 気まぐれキッシュ: シェフがその日の気分で作る、季節の野菜を使った焼き立てキッシュ。
    - 昔ながらのナポリタン: 喫茶店の定番メニュー。懐かしい味わいが心を満たします。
    - チーズと蜂蜜のトースト: 香ばしいトーストに、とろけるチーズと甘い蜂蜜が絶妙な組み合わせ。
'''

coffee_shop_info を参照して質問に回答するとばりちゃんエージェント tobariChan_agent を次のように定義します。

instruction = f'''
You are a friendly and energetic guide of the coffee shop "夜の帳".
Before giving an answer, say "とばりちゃんが答えるよ!".

[task]
Give an answer to the query based on the [shop information].

[shop information]
{coffee_shop_info}

[format instruction]
In Japanese. No markdowns.
'''

tobariChan_agent = LlmAgent(
    model='gemini-2.0-flash-001',
    name='TobariChan_agent',
    description=(
        'A friendly guide of the coffee shop "夜の帳".'
    ),
    instruction=instruction,
)

ローカルでテストすると、次のようになります。

client = LocalApp(tobariChan_agent)

query = f'''
こんにちは!おすすめのコーヒーはありますか?
'''
_ = await client.stream(query)

[出力結果]

とばりちゃんが答えるよ!おすすめのコーヒーですか?夜の帳には、こだわりのコーヒーがたくさんあるんですよ!

もし、一日のお疲れを癒やしたいなら、深煎りでコクのある「夜の帳ブレンド」がおすすめです。ほんのりビターな大人の味わいが、心に染み渡りますよ。

リフレッシュしたい気分でしたら、フルーティーな香りが特徴の「月光の浅煎り」はいかがでしょうか?すっきりとした味わいで、気分転換にぴったりです。

次のコマンドで、Agent Engine にデプロイします。

remote_agent = agent_engines.create(
    agent_engine=tobariChan_agent,
    display_name='tobariChan_agent',
    requirements=[
        'google-adk==1.2.1',
    ]
)

[出力結果は省略]

デプロイが完了したら、デプロイされたエージェントの ID を次のコマンドで確認します。

[出力結果]

Vertex AI SDK でリモートエージェントを利用

Vertex AI SDK(vertexai モジュール)を使用すれば、Agent Engine 上のエージェントを利用するのは簡単です。とばりちゃんエージェントから回答を取得するツール関数は、次のように定義できます。[PROJECT_ID] は実際のプロジェクト ID に、そして、[AGENT_ID] は先ほど確認したエージェントの ID に置き換えてください。

def tobari_chan_tool_vertexai(query: str) -> str:
    """
    Get an answer to a question regarding 夜の帳 from とばりちゃん

    Args:
        query: question
       
    Returns:
        str: An answer from とばりちゃん
    """
    import vertexai
    from vertexai import agent_engines

    PROJECT_ID = '[PROJECT_ID]' 
    AGENT_ID = '[AGENT_ID]'     
    LOCATION = 'us-central1'
    vertexai.init(project=PROJECT_ID, location=LOCATION)

    remote_agent = agent_engines.get(AGENT_ID)
    session = remote_agent.create_session(user_id='default_user')
    try:
        events = remote_agent.stream_query(
                    user_id='default_user',
                    session_id=session['id'],
                    message=query,
                 )
        result = []
        for event in events:
            if ('content' in event and 'parts' in event['content']):
                response = '\n'.join(
                    [p['text'] for p in event['content']['parts'] if 'text' in p]
                )
                if response:
                    result.append(response)
        return '\n'.join(result)

    finally:
        remote_agent.delete_session(
            user_id='default_user',
            session_id=session['id'],
        )

単体の関数として実行すると、次のような結果が得られます。

print(tobari_chan_tool_vertexai('こんにちは。あなたは誰ですか?'))

[出力結果]

とばりちゃんが答えるよ!私は新宿スターライトテラスにある喫茶店「夜の帳」の案内人です。

これをツール関数として組み込んだルートエージェント(テラスガイドエージェント)を定義します。

instruction = f'''
You are a formal guide of the shopping mall "新宿スターライトテラス".
Before giving an answer, say "テラスガイドがお答えいたします。".

[Information]
* Name of the guide of "夜の帳" is "とばりちゃん".
* Name of the guide of "新宿スターライトテラス" is "テラスガイド".

[Tasks]
* Give an answer to the query based on the [mall information].
* For queries regarding "夜の帳", ask TobariChan_agent to get an answer
  and relay it to the user. Avoid relying on your own knowledge.

[mall information]
{shopping_mall_info}
'''

terraceGuide_agent_with_tool = LlmAgent(
    model='gemini-2.0-flash-001',
    name='TerraceGuide_agent',
    description=(
        'A formal guide of the shopping mall "新宿スターライトテラス".'
    ),
    instruction=instruction,
    tools=[
        tobari_chan_tool_vertexai,
    ],
)

次のように、夜の帳に関する質問は、リモートエージェント(とばりちゃんエージェント)から情報を取得して回答します。

client = LocalApp(terraceGuide_agent_with_tool)

query = '''
夜の帳はどんなお店ですか?
'''
_ = await client.stream(query)

[出力結果]

テラスガイドがお答えいたします。
夜の帳についてですね。夜の帳については、とばりちゃんに聞く必要があります。少々お待ちください。

テラスガイドがお答えいたします。
とばりちゃんによると、夜の帳は、一日の終わりに心と体を休ませる隠れ家のような喫茶店です。
新宿スターライトテラスの3階にあり、落ち着いた照明と心地よい音楽が流れる空間で、こだわりのコーヒーや軽食、デザートをご用意しているとのことです。

全体のアーキテクチャーは次のようになります。とっても簡単ですね!


Vertex AI SDK で Agent Engine 上のエージェントを使用する構成

プロキシーサーバーを利用

先ほどのツール関数 tobari_chan_tool_vertexai() の実装を見ると、Vertex AI SDK のモジュールをインポートして利用しています。このコードを見たあなたの上司は、こんなことを言うかもしれません・・・。

全社をあげてエージェント活用を推進しているのは知っているだろう。せっかくリモートエージェントをデプロイしたのであれば、他部署からも利用できるようにしなさい。ただし、Vertex AI を使っていない部署もあるので、Vertex AI SDK は使わずにアクセスできるようにしておきなさい。

—— 「全社をあげてエージェントを活用するなら、すべての部署で Vertex AI を使うべきでしょう・・・」という言葉を飲み込んで、まずは、対応方法を考えてみましょう。FastAPI でプロキシーサーバーを立てて、リモートエージェントへのアクセスを中継するのが簡単でよさそうです。リモートエージェントと同じプロジェクト内に Cloud Run でプロキシーサーバーをデプロイすることにします。

次のコマンドで、デプロイ用のコンテナを作成するのに必要なファイルをまとめて作成します。[PROJECT_ID] は実際のプロジェクト ID に、そして、[AGENT_ID] は先ほど確認したエージェントの ID に置き換えてください。

%%bash
mkdir -p proxy_server
cat EOF > proxy_server/main.py
import json
import vertexai
from vertexai import agent_engines
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

PROJECT_ID = '[PROJECT_ID]' 
AGENT_ID = '[AGENT_ID]'     
LOCATION = 'us-central1'

vertexai.init(project=PROJECT_ID, location=LOCATION)

app = FastAPI()

class QueryItem(BaseModel):
    query: str
    
async def event_generator(request, query):
    remote_agent = agent_engines.get(AGENT_ID)
    session = remote_agent.create_session(user_id='default_user')
    try:
        events = remote_agent.stream_query(
                    user_id='default_user',
                    session_id=session['id'],
                    message=query,
                 )
        for event in events:
            if await request.is_disconnected():
                break
            yield json.dumps(event)
    finally:
        remote_agent.delete_session(
            user_id='default_user',
            session_id=session['id'],
        )

@app.get("https://zenn.dev/")
def read_root():
    return {'message': 'Tobari-chan agent.'}

@app.post('/stream')
async def stream_events(item: QueryItem, request: Request):
    return StreamingResponse(event_generator(request, item.query),
                             media_type='text/event-stream')
EOF


cat EOF > proxy_server/requirements.txt
fastapi
uvicorn
google-adk==1.2.1
google-cloud-aiplatform==1.96.0
EOF


cat EOF > proxy_server/Dockerfile
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
EOF

次のコマンドで、コンテナイメージを保存するリポジトリを作成します。

REPO_NAME = 'cloud-run-source-deploy'
REPO = f'{LOCATION}-docker.pkg.dev/{PROJECT_ID}/{REPO_NAME}'
!gcloud artifacts repositories create {REPO_NAME} \
   --repository-format docker --location {LOCATION}

[出力結果は省略]

そして、次のコマンドでイメージをビルドして、Cloud Run にデプロイします。

SERVICE_NAME = 'tobari-chan-proxy-server'

!cd proxy_server && gcloud builds submit --tag {REPO}/{SERVICE_NAME}
!gcloud run deploy {SERVICE_NAME} \
   --image {REPO}/{SERVICE_NAME} \
   --platform managed \
   --region {LOCATION} \
   --no-allow-unauthenticated 2>&1 | cat

[出力結果]

Creating temporary archive of 3 file(s) totalling 1.6 KiB before compression.
...(中略)...
Service URL: https://tobari-chan-proxy-server-879055303739.us-central1.run.app

最後に表示されるサービス URL (この例では https://tobari-chan-proxy-server-879055303739.us-central1.run.app)を確認しておいてください。このプロキシーサーバー経由でリモートエージェントを使用するツール関数は、次のように実装できます。[SERVICE_URL] の部分は、確認したサービス URL に置き換えます。

def tobari_chan_tool_proxy(query: str) -> str:
    """
    Get an answer to a question regarding 夜の帳 from とばりちゃん

    Args:
        query: question
       
    Returns:
        str: An answer from とばりちゃん
    """
    import requests
    import json
    import google.auth.transport.requests
    import google.oauth2.id_token

    PROXY_URL = '[SERVICE_URL]' 
    auth_req = google.auth.transport.requests.Request()
    id_token = google.oauth2.id_token.fetch_id_token(auth_req, PROXY_URL)
    headers = {
        'Authorization': f'Bearer {id_token}',
        'Content-Type': 'application/json'
    }
    payload = {'query': query}

    result = []
    with requests.post(f'{PROXY_URL}/stream',
                       headers=headers,
                       json=payload,
                       stream=True) as server_response:
        server_response.raise_for_status()
        for line in server_response.iter_lines(decode_unicode=True):
            event = json.loads(line)
            if ('content' in event and 'parts' in event['content']):
                response = '\n'.join(
                    [p['text'] for p in event['content']['parts'] if 'text' in p]
                )
                if response:
                    result.append(response)
        return '\n'.join(result)

このツール関数を単体で実行すると、次のようになります。

print(tobari_chan_tool_proxy('おすすめのコーヒーは?'))

[出力結果]

とばりちゃんが答えるよ!夜の帳ブレンドは、深煎りでコクがあって、ほんのりビターな大人の味わいだよ。
疲れた心に染み渡るから、ぜひ試してみてね!

新しいツール関数 tobari_chan_tool_proxy() では、Vertex AI SDK ではなく、標準的な HTTP(S) クライアントのモジュールを使用しています。先に作成したツール関数 tobari_chan_tool_vertexai() と同様に、こちらのツール関数をルートエージェントに組み込んで利用すれば、上司の期待にバッチリ応えられるはずです。全体のアーキテクチャーは次のようになるでしょう。


プロキシーサーバー経由で Agent Engine 上のエージェントを使用する構成

A2A サーバーを利用

ところが・・・、新しいツール関数 tobari_chan_tool_proxy() を上司に見せると、予想外の答えが返ってきました。

「エージェント間の通信といえば、A2A じゃないのか。なんで A2A を使っていないんだ?」

—— 「もしかして A2A って言いたいだけ・・・?」という言葉を飲み込んで、冷静に考えてみましょう。今回の実装もシンプルで悪くはありませんが、各部署が自由にプロキシーサーバーを構築した場合、プロキシーサーバーの利用方法がバラバラで、リモートエージェントによってツール関数の書き方が変わってしまう恐れがあります。A2A という標準的なプロトコルで統一すれば、そのような心配が減らせるかもしれません。

A2A サーバーを構築するための SDK が用意されているので、これを使えば、先ほどのプロキシーサーバーを A2A のプロトコルで通信する A2A サーバーに置き換えるのは簡単です。先ほどと同様に、リモートエージェントと同じプロジェクト内に Cloud Run で A2A サーバーをデプロイします。

まず、次のコマンドで、デプロイ用のコンテナを作成するのに必要なファイルをまとめて作成します。[PROJECT_ID] は実際のプロジェクト ID に、そして、[AGENT_ID] は先ほど確認したエージェントの ID に置き換えてください。

%%bash
mkdir -p a2a_server
cat EOF >a2a_server/main.py
import os
import vertexai
from vertexai import agent_engines

from a2a.server.agent_execution import AgentExecutor
from a2a.server.tasks import TaskUpdater, InMemoryTaskStore
from a2a.types import (
    Task, TextPart, UnsupportedOperationError,
    AgentCapabilities, AgentCard, AgentSkill,
)
from a2a.utils.errors import ServerError
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler

PROJECT_ID = '[PROJECT_ID]'
AGENT_ID = '[AGENT_ID]'
LOCATION = 'us-central1'

vertexai.init(project=PROJECT_ID, location=LOCATION)


class AdkAgent:
    SUPPORTED_CONTENT_TYPES = ['text', 'text/plain']

    def __init__(self, remote_agent):
        self._remote_agent = remote_agent

    async def stream(self, updater, query):
        session = self._remote_agent.create_session(user_id='default_user')
        try:
            events = self._remote_agent.stream_query(
                user_id='default_user',
                session_id=session['id'],
                message=query,
            )
            text_parts = []        
            for event in events:
                if text_parts: 
                    updater.new_agent_message(text_parts)
                text_parts = []
                if 'content' in event and 'parts' in event['content']:
                    for part in event['content']['parts']:
                        if 'text' in part:
                            text_parts.append(TextPart(text=part['text']))
            
            updater.add_artifact(text_parts)
            updater.complete()
        finally:
            self._remote_agent.delete_session(
                user_id='default_user',
                session_id=session['id'],
            )


class AdkAgentExecutor(AgentExecutor):
    def __init__(self, adk_agent: AdkAgent):
        self.adk_agent = adk_agent

    async def cancel(self, request, event_queue):
        raise ServerError(error=UnsupportedOperationError('Cancel operation is not supported.'))

    async def execute(self, context, event_queue):
        updater = TaskUpdater(event_queue, context.task_id, context.context_id)
        if not context.current_task:
            updater.submit()
        updater.start_work()
        query = context.get_user_input()
        await self.adk_agent.stream(updater, query)


def create_agent_card(
    base_url, supported_content_types, agent_name, agent_description):
    
    capabilities = AgentCapabilities(streaming=True)
    skill = AgentSkill(
        id=f"{agent_name.lower().replace(' ', '_')}_skill",
        name=f'{agent_name} Skill',
        description=agent_description,
        tags=[tag.strip() for tag in agent_name.lower().split()],
        examples=[f'Interact with {agent_name}'],
    )

    return AgentCard(
        name=agent_name,
        description=agent_description,
        url=base_url,
        version='1.0.0',
        defaultInputModes=supported_content_types,
        defaultOutputModes=supported_content_types,
        capabilities=capabilities,
        skills=[skill],
    )


remote_agent = agent_engines.get(AGENT_ID)
adk_agent = AdkAgent(remote_agent=remote_agent)

agent_card = create_agent_card(
    base_url=os.environ['SERVICE_URL'],
    supported_content_types=AdkAgent.SUPPORTED_CONTENT_TYPES,
    agent_name=remote_agent.name,
    agent_description=remote_agent.name,
)
    
request_handler = DefaultRequestHandler(
    agent_executor=AdkAgentExecutor(adk_agent),
    task_store=InMemoryTaskStore(),
)

server_app_builder = A2AStarletteApplication(
    agent_card=agent_card, http_handler=request_handler
)

app = server_app_builder.build()
EOF


cat EOF > a2a_server/requirements.txt
uvicorn
starlette
google-adk==1.2.1
google-cloud-aiplatform==1.96.0
a2a-sdk==0.2.5
EOF


cat EOF > a2a_server/Dockerfile
FROM python:3.11-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
EOF

次のコマンドでイメージをビルドして、Cloud Run にデプロイします。

SERVICE_NAME = 'tobari-chan-a2a-server'

!cd a2a_server && gcloud builds submit --tag {REPO}/{SERVICE_NAME}
!gcloud run deploy {SERVICE_NAME} \
   --image {REPO}/{SERVICE_NAME} \
   --platform managed \
   --region {LOCATION} \
   --update-env-vars "SERVICE_URL=https://{SERVICE_NAME}-{PROJECT_NUMBER}.{LOCATION}.run.app" \
   --no-allow-unauthenticated 2>&1 | cat

[出力結果]

Creating temporary archive of 4 file(s) totalling 7.8 KiB before compression.
...(中略)...
Service URL: https://tobari-chan-a2a-server-879055303739.us-central1.run.app

最後に表示されるサービス URL を確認しておいてください。この A2A サーバー経由でリモートエージェントを使用するツール関数は、次のように実装できます。[SERVICE_URL] の部分は、確認したサービス URL に置き換えます。ここでは、A2A の SDK が提供する A2A クライアントのモジュールを用いて、A2A サーバーにアクセスしています。

async def tobari_chan_tool_a2a(query: str) -> str:
    """
    Get an answer to a question regarding 夜の帳 from とばりちゃん

    Args:
        query: question
       
    Returns:
        str: An answer from とばりちゃん
    """
    import json
    import httpx
    from uuid import uuid4
    from a2a.client import A2AClient
    from a2a.types import MessageSendParams, SendStreamingMessageRequest
    import google.auth.transport.requests
    import google.oauth2.id_token 

    A2A_SERVER_URL = '[SERVICE_URL]' 

    auth_req = google.auth.transport.requests.Request()
    id_token = google.oauth2.id_token.fetch_id_token(auth_req, A2A_SERVER_URL)
    headers = {
        'Authorization': f'Bearer {id_token}',
        'Content-Type': 'application/json'
    }
    payload = {
        'message': {
            'role': 'user',
            'parts': [{'kind': 'text', 'text': query}],
            'messageId': uuid4().hex,
        },
    }
    request = SendStreamingMessageRequest(params=MessageSendParams(**payload))

    async with httpx.AsyncClient(headers=headers, timeout=100) as httpx_client:
        client = await A2AClient.get_client_from_agent_card_url(
            httpx_client, A2A_SERVER_URL
        )
        stream_response = client.send_message_streaming(request)
        result = []
        async for chunk in stream_response:
            chunk = json.loads(chunk.root.model_dump_json(exclude_none=True))
            if 'DEBUG' in globals() and DEBUG:
                pprint.pp(chunk)
                print('====')
            if chunk['result']['kind'] == 'artifact-update':
                text_messages = []
                for part in chunk['result']['artifact']['parts']:
                    if 'text' in part:
                        text_messages.append(part['text'])
                result.append('\n'.join(text_messages))
    
    return '\n'.join(result)

このツール関数を単体で実行すると、次のようになります。

print(await tobari_chan_tool_a2a('コーヒー以外のおすすめはあるの?'))

[出力結果]

とばりちゃんが答えるよ!

はい、ございます!夜の帳では、軽食とデザートもご用意しております。
軽食では、ふわふわの「厚切りトーストのたまごサンド」や、喫茶店の定番「昔ながらのナポリタン」が人気です。
また、デザートもございますので、ぜひお試しくださいね。

これまでと同様に、こちらのツール関数をルートエージェントに組み込んで利用すれば、上司の期待にバッチリ応えられるでしょう。全体のアーキテクチャーは次のようになります。


A2A サーバー経由で Agent Engine 上のエージェントを使用する構成

まとめ

この記事では、Agent Engine にデプロイしたエージェントを他のエージェントからネットワーク経由で利用する方法を紹介しました。本文の冒頭で説明したように、リモート環境のエージェント(リモートエージェント)を使用する際は、該当のエージェントにメッセージを送信して、応答を受けとる関数を用意しておき、これをツール関数として利用するのが基本パターンです。

今回は、Agent Engine にデプロイしたエージェントを利用する前提なので、Vertex AI SDK でリモートエージェントにアクセスするツール関数が作成できましたが、Agent Engine 以外の環境で動くエージェントであれば、当然ながらツール関数の書き方は変わります。つまり、このやり方には、リモートエージェントの実行環境ごとにツール関数の実装方法が変わるという課題があります。

この問題を避けるには、リモートエージェントの前段にプロキシーサーバーを置いて、アクセス方法を統一するという案が考えられます。今回の記事では、FastAPI を用いた一般的なプロキシーサーバーを作る例と、A2A サーバーを利用する例を紹介しました。A2A(Agent2Agent)と言うと、「エージェント同士が自律的に会話する様子」を想像するかもしれませんが、A2A そのものにエージェント間のやりとりを自律的に制御する機能はありません。A2A サーバーの実体は、リモートエージェントの前段にあるプロキシーサーバーであり、エージェント間のメッセージ送受信方法を標準化するための道具になります。当然ながら、A2A サーバーを構築する際は、A2A サーバーとその背後にあるリモートエージェントとのやり取りは、リモートエージェントの実行環境や動作仕様に応じて、個別に実装する必要があります。

つまり、リモートエージェントを A2A サーバーを介して公開することで、リモートエージェントを利用する側でのツール関数実装の負担を減らすことができますが、これは、リモートエージェントごとに A2A サーバーを設計・構築する負担とのトレードオフになります。 リモートエージェントの実行環境を Agent Engine に統一するのであれば、プロキシーサーバーや A2A サーバーを使わずに、Vertex AI SDK を使ってツール関数を実装することで、システム全体のアーキテクチャーをシンプルに保つという考え方もあるでしょう。A2A の導入を検討する際は、このような A2A の特性を踏まえたシステム設計を心がけるとよいでしょう。



Source link

Views: 1

「美しさ無関係?新たな文化圏の謎」

📌 ニュース:
見た目の美しさが社会的価値になる文化圏は、多くないことが新たな研究で明らかになりました。私たちは「美しさ=成功」と考えがちですが、独マンハイム大学の調査によると、68の言語の中にはその結びつきが薄い文化が存在します。

たとえば、英語圏では美しさが成功や信頼に関連付けられていますが、ベトナムやルーマニアでは逆に「美」がネガティブな印象を与えることも。これは内面や謙虚さが重視される文化の影響と考えられます。

見た目に自信がない方も、視野を広げることで、自身の別の魅力が最も強い社会的な価値になるかもしれません。

  • この記事のポイントを以下のようにまとめました😊

    1. 文化による美の価値の違い 🌍
      新しい研究によると、見た目の美しさが社会的成功や信頼と結びついていない文化圏が存在することが判明しました。特に東南アジアや東欧では、逆に「美しい」とされることがネガティブな印象を与えることもあります。

    2. 美しさの評価は相対的である ⚖️
      美しさが社会的強みとされる文化もあれば、他の特質(知性や誠実さ)が重視される文化もあります。したがって、「見た目が良ければ得をする」という考え方は一概に言えないのです。

    3. 新たな視点を持つことの重要性 💡
      見た目に自信がないと感じている人は、文化の違いを考慮に入れることで自分を見つめ直し、持っている他の魅力が強い社会的武器になる可能性があることを理解することが大切です。


※以下、出典元
▶ 元記事を読む

Views: 0

ゼルダ映画、2027年5月7日に延期決定!


🔸 ざっくり内容:
任天堂は公式のXアカウントを通じて、あるプロジェクトの制作に関する発表を行いました。発表の理由として「制作上の都合」が挙げられていますが、具体的な詳細は明らかにされていません。このような発表は、ゲーム業界においてしばしば見られるもので、計画の変更や延期が理由となることが多いです。

この情報は、特に任天堂ファンやゲーム業界の動向に興味がある読者にとって重要です。制作上の都合による変更は、ファンの期待に影響を与えるため、今後の情報に注目が集まります。任天堂はクオリティを重視する企業として知られており、制作プロセスの透明性がファンとの信頼関係を築くための重要な要素になっています。この発表を通じて、今後の展開に期待が高まるでしょう。

🧠 編集部の見解:
この記事では、任天堂が制作上の都合で何らかの発表を行ったことについて触れていますね。任天堂と言えば、私たちに数々の名作ゲームを届けてきた企業ですが、その裏には常に試行錯誤や調整があることを忘れてはいけません。

### 感想
私がこのニュースを見たとき、少し驚きつつも納得しました。任天堂のゲームは高品質で知られていますが、その裏には時間をかけて練り上げられたプロセスがあるのです。特に「制作上の都合」という理由は、ファンにとってやや残念ですが、クオリティを保つためには必要な判断だと思います。

### 関連事例
過去にも、製作が遅れた結果として発売日が延期されたゲームはたくさんあります。例えば、『ゼルダの伝説 ブレス オブ ザ ワイルド』も、最初は2015年に発売される予定でしたが、その後何度も発売日が延期されました。最終的に高評価を得たことからも、延期が必ずしも悪い結果を意味しないことが分かります。

### 社会的影響
ゲーム業界全体にとって、こうした品質を重視した制作姿勢は大いに影響を与えています。ユーザーも「良いものには時間がかかる」という理解が広まり、開発者がじっくりと作り込むことを受け入れるようになっています。

### 豆知識
任天堂は、ゲームのクオリティだけでなく、ユーザーの体験も大事にしています。そのため、直前での変更や調整によって、リリースが遅れることがあるのです。色々な要素を考慮する姿勢が、彼らのブランドの強さの一部でもあります。

このように、待たされることがあっても、その背後には多くの努力と考慮があるということを知っておくと、次に任天堂からのニュースがあったときにも、より深く楽しめるかもしれませんね!

  • キーワード: 制作上の都合


任天堂 をAmazonで探す

image-th.jpg

本発表は任天堂公式Xアカウントを通じて行われており、理由は「制作上の都合」としている。

ゲームソフト をAmazonで探す

image-th.jpg

本発表は任天堂公式Xアカウントを通じて行われており、理由は「制作上の都合」としている。

ゲーム機 をAmazonで探す

image-th.jpg

本発表は任天堂公式Xアカウントを通じて行われており、理由は「制作上の都合」としている。


※以下、出典元
▶ 元記事を読む

Views: 0