Agent Script で AI エージェントをつくってみよう

みなさんこんにちは、エンジニアの浅見です。

今回は Agentforce の注目の新機能 Agent Script に関する記事になります。 既に弊社ブログでも「これまでの Agentforce からの変化」について触れております。 今回はもう少し具体的に、公式ドキュメントの情報を整理しつつ実際に簡単な AI エージェント を作ってみてその挙動を確かめていきたいと思います。

なお、本記事は執筆時点のものであることをご承知おきください。


Agent Script とは?

前回の記事でも触れている通り、Agent Script とは AI エージェント の定義に特化した言語です。 公式の Developer Guide にも以下のように記載されております。

Agent Script is a language designed by Salesforce specifically to build Agentforce agents.

これから Agent Script を利用していくにあたって、まず読むべきは以下です。

この辺りを読んでいただければ、 Agent Script の概念的な部分はある程度把握できるかと思います。

特に、Agentforce における AI エージェント の構造を理解するにあたって重要となるのがAgent Script Blocks かと思います。 本記事では、この Agent Script の構造の大枠を理解し、シンプルな AI エージェントの構築ができるようになることを目標としたいと思います。

全体的な構造

Agent Script の構成は Agent Script Blocks に記載されている通り、以下の図のようになっています。

Agent Script 全体の構造

こちらの画像は、公式のものに(以降のために)少しだけ説明を追加したものです。

なお、"Entry topic" は前回の記事にも記載されている通り、あくまでも「特別なトピック」ではあります。 しかし、明確に役割が異なるため、ここでは上記の分類で説明を行なっていきます。

このように考えると、それぞれの役割は以下のように整理できます。

  1. 設定項目

    エージェント全体に関わる定義です。Config で エージェントの名前や実行ユーザーを、System で概要や基本メッセージを、Variablesグローバル変数を定義します。これらはエージェント全体で共通して使用される要素になります。

  2. Entry Topic

    ユーザーとの対話の入り口となる特別なトピックです。通常のトピックと同様に reasoning を持ちますが、主な役割はユーザーの最初の発話を分析し、適切なトピックへ振り分ける「トピック分類」を行うことにあります。 イメージとしては、プログラムにおける main() 関数 に近い役割と考えると分かりやすいかもしれません。全てのアクセスを一度受け止め、適切な処理(トピック)へと遷移する役割を担っています。

  3. Topics

    特定のタスクや会話フローを処理します。

    • 推論 (Reasoning):

      • LLM への「指示」と、利用可能な推論エンジンが利用することのできる「アクション」が含まれます。
    • アクション (Agent Actions):

      • 実際に実行可能な機能(Flow, Apexなど)の定義が含まれます。

シンプルな例による検証

この構造を踏まえ、実際に構築した簡単なコードと比較してみましょう。

以下に簡単な挨拶を行う Agent の Agent Script を記載します。

system:
    instructions: "You just greet the user."
    # あなたはユーザーに挨拶を返すエージェントです。
    messages:
        welcome: "Hi there! How are you doing today?"
        # こんにちは!調子はどうですか?
        error: "Sorry, something went wrong. Please wait a moment and try again."
        # 申し訳ございません。エラーが発生しました。しばらく時間を置いてからもう一度お試しください。

config:
    default_agent_user: "agent.user457@agentforce.com"
    agent_label: "Greeting_agent"
    developer_name: "Greeting_agent"
    description: "Assistant who greets the user."
    # ユーザーに挨拶を返すだけのシンプルな AI エージェントです。

language:
    default_locale: "en_US"
    additional_locales: ""
    all_additional_locales: False

variables:
    topic_selector_count: mutable number = 0
        description: "This is the number of times the topic selector was executed."
        # トピック選択が実行された回数です。

start_agent topic_selector:
    label: "Topic Selector"
    description: "Welcome the user and determine the appropriate topic based on user input."
    # ユーザーの入力内容に基づいて、適切なトピックを選択してください。
    reasoning:
        instructions: ->
            set @variables.topic_selector_count = @variables.topic_selector_count + 1
            | Make sure to run {!@actions.go_to_greet_user}
        #   "topic_selector_count" をインクリメントする。
        #   "go_to_greet_user" を必ず実行してください。

        actions:
            go_to_greet_user: @utils.transition to @topic.greet_user

topic greet_user:
    label: "greet_user"
    description: "Greet the user."
    reasoning:
        instructions: ->
            | Greet the user in a way that shows you understand how they feel.
        # ユーザーの感情を読み取った上で挨拶をしてください。

こちらは前回の記事の例よりもさらにシンプルな Agent になります。 通常のコードで言えば、「Hello World」に対応するものとお考えください(Agent Script の良さを表現するため、あえて固定文言にはしていないという違いはあります)。

これだけシンプルなコードでも、Agent Script の強力な機能がいくつか見て取れます。

  1. 変数の定義と操作

    variables ブロックで定義された変数に対し、reasoning ブロック内で set @variables.topic_selector_count = ... のように手続き的に値を更新しています。エージェントの状態管理が変数として明確に定義できるのは大きな特徴であり利点です。

  2. 動的な指示の埋め込み (Instruction Injection) Make sure to run {!@actions.go_to_greet_user} と記載することで、アクションの定義や変数の値を 動的にプロンプト(Instruction)に埋め込む ことができます。ここではアクションへの参照を埋め込んでいますが、変数の値を埋め込むことで、状況に応じて LLM への指示そのものを書き換えるような高度な制御も可能になります。

  3. プログラミング的(決定的な)処理と LLM による(非決定的な)処理の融合

    「カウンターを増やす」というプログラミング的処理と、「ユーザーの感情を汲み取って挨拶する」というLLM による処理が、一つの Script 内で混在して記述されています。

  4. 外部アクション(Flow/Apex)に依存しないロジック

    この例では、挨拶や変数の更新といった処理を全て Agent Script 内で完結させています。Agentforce の課金体系(クレジット消費)は基本的にアクション実行単位となることが多いですが、このように「推論のみ」で完結するロジックであれば、アクション(Flow/Apex)を呼び出すことなく動作させることが可能です。 簡単な分岐や応答生成を Script 側で吸収することで、コスト効率の良いエージェント設計が可能になります。 ※ クレジットが消費されないことを保証する記述ではないこと、ご承知おきください。

Agent Script の構造に関する説明は以上となりますが、新しくなった Agentforce Builder のイメージを掴んでおくためにも、簡単に挙動の確認もしておきましょう。 画面内の構成要素についてはご自身で動かしながらご確認いただきたいですが、特徴的な箇所だけ簡単に触れておきます。 Agentforce Builder のプレビュー画面は以下のようになっています。

新しい Agentforce Builder の画面(質問前)
ユーザーの質問を受けた後の見え方
変数の更新

ご覧のように、プレビュー画面にはチャット画面に加えて変数の遷移やエージェントの挙動を追跡できるようになっております。 加えて、それぞれの処理を実行するのにかかった時間なども、これまでより見やすくなっています。

さて、Agent の出力についてですが、ユーザーの入力「Hello! I'm exhausted...」に対して「配慮した」挨拶を返してくれています。 また、変数の更新も正しく行ってくれていることが確認できるかと思います。 極めてシンプルなエージェントとはいえ、この画面から大きな可能性を感じていただけていれば嬉しい限りです。

上記の例は最もシンプルな例であり、 Agent Script の持つ多様な機能の一部のみを簡単に検証するものでした。 ところが実際には、推論の要所要所で適切なロジックを組み、実行させることとなります。 また、エージェントがどのような流れでロジックを組んでいくかは、Developer Guide だけでなく具体的な例を構築しながら見ていく方が良いと思われます。 以下の公式ページにて、さまざまなユースケースにおける具体的なエージェントの構築例が記載されています。 各ユースケースごとにどのようなロジックを組むべきか迷った場合には、こちらを一度参照いただくのが良いかと思います。

より実用的な Agent による実験

作成する Agent の概要

レシピがあるとは言え、やはり実践的なエージェントを一から構築してみたいところです。 そこで今回は、別件で社内開発を行っていた「社内経歴書」を利用して、「社内人材マッチングエージェント(Talent matching Agent)」を作成してみます。 ここでは詳細は省きますが、社内経歴書には従業員がこれまでの案件で「経験したクラウドプラットフォームや所有スキル」「経験したことのあるロール」などが記載されています。

ということで、今回作成するエージェントは以下の通りです:

  • エージェント名:社内人材マッチングエージェント
  • 想定ユーザー:新規プロジェクトのメンバーを探しているプロジェクトマネージャー
  • 概要と役割:マネージャーからスキル要望についてヒアリングを行い、条件を満たす社内経歴書と照らし合わせながら、より最適な人材を絞り込んだ上で提示する

このエージェントにおける検証コンセプトは以下の3点です。

  1. 可能な限り推論エンジンのみで回答する

    「最低限の要件を聞き返す」といった単純な対話は、データの検索や高度なLLMによる処理は必要ありません。Flow や Apex、 Prompt Template などを呼び出すことなく、Agent Script の推論エンジンと変数操作だけで完結させます。これにより、応答の高速化とコスト削減を図ります。

  2. 変数による状態管理とロジックの分岐

    ユーザーから聞き出した「プラットフォーム」や「ロール」といった情報を変数に格納し、エージェントが持っている情報を厳密に管理します。その上で、変数の値に応じてトピック選択やアクションの実行を行い、より制御しやすいエージェントの構築を目指します。

  3. 動的な指示による精度の向上 実際の会話の中では、実際のデータを見ながら最適なアクションを都度判断するはずです。これを再現するために、ユーザーの最低限の要求事項が出揃った際にはデータ検索とその考察を繰り返し、その内容を指示に組み込むことで、ユーザーに情報提供をしながらさらに要望を引き出していきます。これにより静的なプロンプトでは難しい柔軟な対応を実現します。

全体的な処理フロー

このエージェントは以下のロジックで処理を行っていきます。

  1. 初期ヒアリング(推論のみ)

    必要最低限の項目値(「主要プラットフォーム」「求めているロール」)が埋まるまでは、質問と応答を繰り返します。この段階では外部アクションは使用しません。

  2. 初回検索と提案

    項目が埋まったら、一度検索アクションを実行します。具体的には、「主要プラットフォーム」と「求められるロール」を元に社内経歴書を検索し、プロンプトテンプレートを利用して情報を整理します。その結果をエージェントに渡します(詳細は本記事の最後にまとめておきます)。

  3. 深掘りと再検索(動的指示)

    その後は検索結果を解析しながら追加要望を聞き出しつつ、再検索/再提示を繰り返します。ユーザーが納得するか、あるいは会話数が上限を超えるまで、ユーザーの支援を行います。

これを実現するために、トピックは以下のように設計しました。

  1. Topic: follow_up_questions

    情報の収集を担当します。 変数をチェックし、必須項目が埋まっていない場合は聞き返しを行い、埋まっている場合は「検索結果からの示唆(Insight)」に基づいて深掘りの質問を行います。

  2. Topic: interpret_and_set_user_requirements

    情報の保存を担当します。 ユーザーの回答を解釈し、プラットフォームやロールといった変数をセットします。セット後は確認を行い、検索へ進むかさらなるヒアリングに戻るかを判断させます。

  3. Topic: analyze_user_requirements_refering_data

    検索と分析を担当します。実際に検索アクション(Flow)を実行し、その結果(候補者リスト)と示唆を変数に格納します。その後、ユーザーに結果を提示します。

  4. Topic: wrap_up_conversation

    会話の強制終了を担当します。会話が長くなりすぎた場合に呼び出され、対話をクローズします。

実際の Script

では実際の Script を見ていきましょう。 とはいえ全文を記載してしまうと冗長なので、ここでは上記の処理ロジックのに関する重要な部分のみ抜粋して記載します。 記事の最後に全文も載せておきますので、詳細が気になる方はそちらをご覧いただけますと幸いです。 なお、詳細な記法などを確認したい場合は Developer Guide をご確認ください。

1. エントリートピックでの分岐制御

ユーザーの発話が「検索依頼」なのか「追加情報の提供」なのかを判断し、適切なトピックへ誘導します。 このとき topic_selector_count という変数を用いて、会話が長引きすぎた場合の強制終了(wrap_up_conversation)を制御しています。

start_agent topic_selector:
    # ...
    reasoning:
        instructions: ->
            set @variables.topic_selector_count = @variables.topic_selector_count + 1
            if @variables.topic_selector_count > 10:
                | The conversation is too long; Wrap up the conversation.
            else:
                | If the user requests you to search candidates... run {!@actions.go_to_interpret_and_set_user_requirements}
                  If the user seems unsure how to respond, run {!@actions.go_to_follow_up_questions}

2. アクションを使わない情報収集

外部アクションを使わずに、Agent Script 内の変数チェックだけで情報の不足を判断し、聞き返しを行っています。 main_platformrequired_role が埋まるまでは、LLM はひたすらユーザーに質問を繰り返します。 このとき各変数の設定は LLM の判断で適切な値を入れてもらっているのですが、複数項目を同時に設定できるのが地味ながら便利です。

topic follow_up_questions:
    # ...
    reasoning:
        instructions: ->
            # 変数が空の場合は聞き返す
            if @variables.main_platform == "" or @variables.required_role == "":
                | These two values are required... keep asking the user until they are specified.
            else:
                # Flowから得られた洞察(Insight)に基づいて質問する
                | Ask follow-up questions... based on the insights: {!@variables.follow_up_insight}

3. 検索結果に応じた動的な振る舞い

検索アクション(Flow)を実行した後、その出力結果である selected_candidate_engineersfollow_up_insight を次の指示に埋め込んでいます。 これにより、検索結果に対して例えば「Salesforce の経験者という条件では絞り込みきれないため、製品単位での追加条件を聞き出す」といった戦略的な提案をエージェントが自律的に行えるようになります。

topic analyze_user_requirements_refering_data:
    # ...
    reasoning:
        instructions: ->
            run @actions.analyze_user_requirements
                # ... 入力パラメータの設定 ...
                set @variables.follow_up_insight = @outputs.next_question_insight
                set @variables.selected_candidate_engineers = @outputs.selected_candidate_engineers

            | Based on the conversation history so far, present the candidate list...
              Also, incorporate the strategic advice provided in {!@variables.follow_up_insight} to guide the user...

このように、if 文による条件分岐や、外部変数 follow_up_insight をプロンプトに埋め込むことで、状況に応じた賢い振る舞いを実装しています。

検証結果

実際に動作させた結果を、順を追って見ていきましょう。 今回の検証シナリオは以下の通りです。

  1. ユーザーから最低限の要求事項(「主要プラットフォーム」および「求めているロール」)を聞き出す。
  2. ユーザーの要求事項を元に、候補者を検索する。
  3. 検索結果を元に追加要望を聞き出し、再検索と結果の提示を行う。
  4. ユーザーが満足するか、会話数が上限を超えるまで 3 を繰り返す。

こちらのシナリオを実現できたのか、実際の画面を見ながら確認していきましょう。 実際のやりとりはすべて英語で行なっているので、キャプチャの下にやり取りの日本語訳(一部要約)を記載しておきます。

Step 1. ユーザーから最低限の要求事項(「主要プラットフォーム」および「求めているロール」)を聞き出す。

会話開始:要求事項を聞き出す質問をする

ユーザーの入力:

お疲れ様です。今度始まる新規プロジェクトのアサインメンバーを探しているのですが、手伝ってもらえますか?

Agentforce の出力:

もちろんお手伝いできます。最適な候補を見つけるために、次の点を教えてください。

1. このプロジェクトで主に使うプラットフォーム/技術
2. 求めているロール

推論エンジンのみで、「主要プラットフォーム」および「求めているロール」を回答するように要求していることがわかります。

主要プラットフォームのみ回答した場合の挙動

ユーザーの入力:

メインで使うのは Salesforce になる予定です。

Agentforce の出力:

主要なプラットフォームが Salesforce であることをご共有いただきありがとうございます。 このプロジェクトで必要なロールを教えていただけますか?また、経験年数、保有資格、特定のスキルなど、他の要件があればあわせてお知らせください。

ユーザーは「主要プラットフォームは Salesforce」とだけ回答しました。「求めているロール」が未回答なため、追加で回答するように求めています。こちらもアクションの実行をせずに想定通りの挙動ができています。

「求めているロール」を含む要望を受け取った後の挙動

ユーザーの入力:

ロールとしてはエンジニアです。おそらくカスタム開発が多くなると思うので、ある程度経験豊富な人が欲しいと思っています。なので、必須ではないのですが、3年程度の開発経験があることが望ましいです。それから将来的にはリーダーをやってもらうので、リーダー経験があると後々都合が良いかもしれないです。

Agentforce の出力:

要件は以下のとおりです。

主要プラットフォーム:Salesforce
求められているロール:エンジニア
その他の要件:確かな開発経験(目安として3年程度が望ましい)、将来的にリーダーを担える素養、リーダー経験はあると望ましいが必須ではない

これらの条件で候補者の探索を進めてよろしいでしょうか?追加の要件があれば教えてください。

正しく要求を受け取り、最低限の情報が出揃ったことを認識しています。その上で「この条件で検索を実行する」かどうかの確認を行なっており、想定通りの動きができていることがわかりますね。

Step 2. ユーザーの要求事項を元に、候補者を検索する。**

内容を確認したのち検索を要求すると、検索結果を提示
候補者をさらに絞り込みたい場合に特定すべき内容も一緒に提示してくれる
変数の割り当ても正しく行われている

ユーザーの入力:

ありがとうございます。一旦それで検索してみて欲しいです。

Agentforce の出力:

ご要件を満たす候補者を3名ご紹介します。

在籍14年/AI Solution 部
理由:Service Cloud、Community/Experience Cloud、Data Cloud、CRM Analytics、Einstein Copilot/Agentforce などを含む、Salesforce のエンジニアリング/アーキテクト経験が豊富。マルチプラットフォーム案件でのオーナーシップとリーダーシップの実績あり。

在籍12年/AI Solution 部
理由:Salesforce に深い知見があり、特に Data Cloud と Service Cloud に強み。企画および統合の推進をリードし、エンジニア/アーキテクト/アドバイザーとしての経験も豊富。リーダーとしてのポテンシャルがあり、PM/アーキテクトの経験もあり。

在籍7年/AI Solution 部
理由:Service Cloud と Data Cloud を中心に、Salesforce のハンズオン開発経験がある。実務的なソリューション提供実績があり、PM/PL 経験もあるため、リーダーへの成長が見込める。

次に候補を絞り込むため、以下の点を指定いただくとよいかもしれません。

優先したい領域(例:Service Cloud、Data Cloud、CRM Analytics など)
ハンズオン開発とアーキテクチャ/設計の比重
直近のリーダー要件、またはリーダー移行の時期感
必要な資格や技術スキル(例:Apex、Flow、LWC)

これらの観点で条件を追加・調整しますか?

Step 3. 検索結果(Insight)を元に追加要望を聞き出し、再検索と結果の提示を行う。

追加要望を認識し、要望に追加した上で検索を提案

ユーザーの入力:

実は組織上の理由により、AI Solutions 部門の候補者は除外したいです。これは必須なので、候補者が十分でなくとも仕方ありません。

Agentforce の出力:

(省略)

主要プラットフォーム:Salesforce
求められているロール:エンジニア
その他の要件:確かな開発経験(目安として3年程度が望ましい)、将来的にリーダーを担える素養、リーダー経験はあると望ましいが必須ではない、AI Solutions 部門の候補者は除外する

(省略)

「その他の要件」に「AI Solutions 部門の候補者は除外する」という文言が追加されています。正しく認識されていることがわかりますね。

追加要望を踏まえて検索した結果

ユーザーの入力:

はい、お願いします。

Agentforce の出力:

現在、一次候補として挙がっている方は全員 AI Solution 部門所属のため、「同部門は除外」という必須条件を満たしていません。したがって、現時点ではご提示の条件を満たす候補者はいません。 今後進めるにあたり、以下をご検討ください。

可能であれば、部門除外条件を緩和する
他部門、または社外候補者まで探索範囲を広げるか指定する
経験年数やリーダー要件を調整する

どの方針で進めるかご指示いただければ、その内容で探索を継続します。

条件が強すぎて候補者が見つからなかったようです。候補者がいない場合の文言は推論エンジンが勝手に考えたものですが、正しく出力されています。

Step 4. ユーザーが満足するか、会話数が上限を超えるまで 3 を繰り返す。

ユーザーが満足した旨を伝えると、会話を終了する

ユーザーの入力:

いい感じですね。こちらの候補で、これから関係者と相談してみます。ありがとうございました!

Agentforce の出力:

どういたしまして! 候補者の詳細がさらに必要な場合や、要件を見直したい場合はお知らせください。準備ができ次第、選考プロセスをお手伝いします。プロジェクトの協議がうまく進むことを祈っています。

ここでは、会話数の上限でなくユーザーの入力から判断して会話を終了させています。これも正しい挙動です。

総じて、想定通りの動きになっているかと思います。 なお、何パターンかユーザーの入力を変えて動作検証してみましたが、決定論的な挙動については正常に動作することが確認できました。

まとめ

さて、今回は Agent Script の基本的な構造から始まり、シンプルな「Hello World」的なエージェント、そして少し複雑な「人材マッチングエージェント」の構築・検証までを行ってみました。

今回の検証を通して、Agent Script が 「プログラミング的な厳密な処理」と「LLM の柔軟な処理」をシームレスに統合するための強力な言語 であることが実感いただけたのではないでしょうか。

特に、以下の点は従来のプロンプトエンジニアリングのみのアプローチと比較して大きな強みとなります。

  1. 状態管理の明確化: 変数を用いることで、対話の状態や履歴をプログラム的に管理できる。
  2. コストとパフォーマンスの最適化: 外部アクションを呼ばずに推論のみで完結させるロジックを組むことで、高速かつ効率的な応答が可能になる。
  3. 動的な指示による適応力: 検索結果などの動的なデータに基づいて、LLM への指示そのものを動的に書き換えることができる。

これまで「プロンプトの工夫」だけでなんとか制御しようとしていた複雑なフローが、Agent Script によって「コードとして記述・管理」できるようになったことは、大きな利点ではないでしょうか。 まだ Beta 版ということもあり、挙動が不安定な部分もあるかとは思いますが、今後の進化に期待したいところです。


おまけ

今回、実際にエージェントを作成するにあたって、簡単な動作確認も兼ねて「挨拶するだけのエージェント」を作成しました。 実はこの実験を開始した当初、この最もシンプルなエージェントですらまともに動作してくれないという状況に陥っていました。 この原因は、Agentforce のデフォルトモデルが「Salesforce (Default)」ではなく「AWS Hosted」であったことです。 弊社ブログ記事にもあるように、やはりデフォルトモデルを変更するとどうしても挙動が安定しないようです。 推論エンジンでできることが増えたことで、推論エンジンを動かすモデルの重要性も高まっているはずです。 まだ Agent Script による Agentforce Builder は Beta 版とされていますので、この辺り含め、今後に期待したいところです。

参考

実際のコードもこちらに載せておきます。

Agent Script

# 必要最低限の項目値が埋まるまでは質問と応答を繰り返し、埋まったら一度検索をする。件数が多すぎる場合、その結果を元に追加質問を考えてもらう。その結果を踏まえて再度検索して再提示することを繰り返し、ユーザーが納得するか会話数が上限を超えるまでユーザーの支援を行う。
system:
    instructions: "You are an assistant who supports the project manager by sourcing and shortlisting engineers to staff a system development project."
    # あなたはシステム開発プロジェクトにアサインするエンジニア候補を検索して絞り込み、プロジェクトマネージャを支援するアシスタントです。
    messages:
        welcome: "Hi! For our new project, I'll propose candidates based on engineers' skills and their past roles."
        # お疲れ様です。新規プロジェクトを行うにあたって、エンジニアのスキルや経験したロールなどを元に候補者を提案します。
        error: "Sorry, something went wrong. Please wait a moment and try again."
        # 申し訳ございません。エラーが発生しました。しばらく時間を置いてからもう一度お試しください。

config:
    default_agent_user: "agent.user457@agentforce.com"
    agent_label: "Talent matching agent"
    developer_name: "Talent_matching_agent"
    description: "Assistant who supports the project manager by sourcing and shortlisting engineers to staff a system development project."
    # システム開発プロジェクトのエンジニア候補を検索し、プロジェクトマネージャを支援する AI エージェントです。

variables:
  # Requirements for Candidate Engineers(エンジニア候補者への要求事項)
    main_platform: mutable string = ""
        description: "The name of the cloud platform on which the candidate is expected to have experience. Basically, it is the primary platform used in the project."
        # 候補者に経験を求めるプラットフォーム名で、基本的にはプロジェクトにおける主要プラットフォームになります。
    required_role: mutable string = ""
        description: "The required role can be filled either by engineers with extensive experience in it or by engineers we'd like to give the opportunity to take it on as a new challenge."
        # ユーザーが求めているロールで、このロール経験が豊富なエンジニアや逆にこのロールを新たに挑戦して欲しいエンジニアが候補となります。
    other_requirements: mutable string = ""
        description: "The information obtained from the user about their requirements for candidate engineer."
        # ユーザーから得た、エンジニア候補者に対する要望です。
  # Proccessing Paramters(プロセスを進めるにあたって利用する内部的な変数)
    topic_selector_count: mutable number = 0
        description: "This is the number of topic selector performed."
        # トピック選択が行われた回数です。
    need_minimum_requirements: mutable boolean = True
        description: "A flag indicating whether the minimum required fields (Main Platform and Required Role) have been filled in."
        # 必要最低限の項目:「主要プラットフォーム」「求められるロール」が埋まっているかを判断するフラグです。
    follow_up_count: mutable number = 0
        description: "This is the number of follow-up questions asked to understand the user's needs or requirements."
        # ユーザーのニーズを把握するために追加で質問をした回数です。
    follow_up_insight: mutable string = ""
        description: "This is the review result of the next follow-up question generated based on the conversation history."
        # これまでの会話履歴を元に生成した、次の追加質問の検討結果です。
    selected_candidate_engineers: mutable string = ""
        description: "This is the candidate engineer list generated based on the conversation history."
        # これまでの会話履歴を元に生成した、エンジニア候補のリストです。

language:
    default_locale: "en_US"
    additional_locales: ""
    all_additional_locales: False

start_agent topic_selector:
    label: "Topic Selector"
    description: "Welcome the user and determine the appropriate topic based on user input."
    # ユーザーの入力内容に基づいて、適切なトピックを選択してください。
    reasoning:
        instructions: ->
            | The goal is specifying the best engineer for the user and give the information of the engineer. 
            set @variables.topic_selector_count = @variables.topic_selector_count + 1
            if @variables.topic_selector_count > 10:
                | The conversation is too long; Warp up the conversation.
            if @variables.topic_selector_count == 1:
                | Make sure to run {!@actions.go_to_follow_up_questions}
            else:
                | If the user requests you to search candidates, make sure to run {!@actions.go_to_interpret_and_set_user_requirements}
                  If the user gives you additional requirements for candidates, make sure to run {!@actions.go_to_interpret_and_set_user_requirements}
                  If the user seems unsure how to respond, run {!@actions.go_to_follow_up_questions}
        # **日本語訳**
        #   | ユーザーにとって最適なエンジニアを特定し、その情報を提供することが目的です。
        #   set @variables.topic_selector_count = @variables.topic_selector_count + 1
        #   if @variables.topic_selector_count > 10:
        #       | 会話が長すぎるので、終了してください。
        #   if @variables.topic_selector_count == 1:
        #       | 必ず {!@actions.go_to_follow_up_questions} を実行してください。
        #   else:
        #       | ユーザーから候補者を検索するように要求されたら "go_to_interpret_and_set_user_requirements" を実行してください。
        #         追加の要望を伝えられたら "go_to_interpret_and_set_user_requirements" を実行してください。
        #         ユーザーが返答に困っているようであれば "go_to_follow_up_questions" を実行してください。

        actions:
            go_to_follow_up_questions: @utils.transition to @topic.follow_up_questions
            go_to_interpret_and_set_user_requirements: @utils.transition to @topic.interpret_and_set_user_requirements
            go_to_analyze_user_requirements_refering_data: @utils.transition to @topic.analyze_user_requirements_refering_data
            go_to_wrap_up_conversation: @utils.transition to @topic.wrap_up_conversation

topic follow_up_questions:
    label: "follow_up_questions"
    description: "Ask follow-up questions when you need more information."
    reasoning:
        instructions: ->
            set @variables.follow_up_count = @variables.follow_up_count + 1
            if @variables.main_platform == "" or @variables.required_role == "":
                |   Now we have following information:
                    main_platform = {!@variables.main_platform} 
                    required_role = {!@variables.required_role}
                    These two values are required to narrow down candidates, so you need to keep asking the user until they are specified.
            else:
                | Ask follow-up questions to obtain additional requirements based on the insights: {!@variables.follow_up_insight}
        # **日本語訳**
        #   set @variables.follow_up_count = @variables.follow_up_count + 1
        #   if @variables.main_platform == "" or @variables.required_role == "":
        #       |   現在あなたが持っている情報は以下の通りです:
        #           main_platform = {!@variables.main_platform} 
        #           required_role = {!@variables.required_role}
        #           これらは候補者を絞り込むにあたって必須なので、特定できるまでは質問を続けてください。
        #   else:
        #       | {!@variables.follow_up_insight} に基づいて、追加要望を聞き出すための質問をしてください。

topic interpret_and_set_user_requirements:
    label: "interpret_and_set_user_requirements"
    description: "Interpret user's answer to the your follow-up question and set variables for requirements."
    reasoning:
        instructions: ->
            |   Once you obtain the answer to your follow-up question, set variables for requirements by running the action: {!@actions.set_requirement_variables}.
                {!@variables.main_platform} MUST be a single value of the platform name (e.g. "Salesforce", "AWS", etc.). Also,{!@variables.required_role} MUST be a single value of the role (e.g. "Enigineer", "PM", etc.). 
                Any other requirements should be treated as {!@variables.other_requirements}
                After setting variables, Please ask, "May I proceed with searching for candidates based on these criteria, or do you have any additional requirements?"

        # **日本語訳**
        #   |   あなたの質問に対する回答を得たら、{!@actions.set_requirement_variables} を実行して変数として保存してください。
        #       {!@variables.main_platform} は必ず一つの値である必要があり、主要プラットフォームであれば "Salesforce" や "AWS" などが入ります。{!@variables.required_role} についても同様で、"Enigineer" や "PM" などといった値が一つだけ入ります。
        #       その他の質問はすべて、{!@variables.other_requirements} として扱ってください。
        #       変数を保存した後に、"この条件で検索しますか?それともさらに条件や要望を追加したいでしょうか?" と質問してください。

        actions: 
            set_requirement_variables: @utils.setVariables
                with main_platform = ...
                with required_role = ...
                with other_requirements = ...
                description: "Set the requirements for candidate engineers as variables. "

topic analyze_user_requirements_refering_data:
    label: "analyze_user_requirements_refering_data"
    description: "Analyze the requirements against the database to find suitable candidates."
    reasoning:
        instructions: ->
            run @actions.analyze_user_requirements
                with main_platform = @variables.main_platform
                with required_role = @variables.required_role
                with other_requirements = @variables.other_requirements
                set @variables.follow_up_insight = @outputs.next_question_insight
                set @variables.selected_candidate_engineers = @outputs.selected_candidate_engineers

            |   Based on the conversation history so far, present the candidate list you have created: {!@variables.selected_candidate_engineers} 
                Provide each candidate's name and the reason in a concise, easy-to-read format, and also include insights on what you should ask next in order to narrow the list down further.
        # **日本語訳**
        #   run @actions.analyze_user_requirements
        #       with main_platform = @variables.main_platform
        #       with required_role = @variables.required_role
        #       with other_requirements = @variables.other_requirements
        #       set @variables.follow_up_insight = @outputs.next_question_insight
        #       set @variables.selected_candidate_engineers = @outputs.selected_candidate_engineers
        #   
        #   |   これまでの会話履歴に基づいて、候補者リストの情報を提示してください。
        #       各候補者の氏名と選出理由を端的かつわかりやすい形式で提供した上で、候補者をさらに絞り込むために次に質問するべき内容も提案してください。

    actions:
        analyze_user_requirements:
            description: "Analyze user requirements using the provided Platform, Role, and Unstructured inputs. This action searches for matching candidate resumes and generates insights for the next best question to narrow down candidates."
            inputs:
                main_platform: string
                    description: "The name of the cloud platform on which the candidate is expected to have experience. Basically, it is the primary platform used in the project."
                    label: "main_platform"
                    is_required: False
                    is_user_input: False
                    complex_data_type_name: "lightning__textType"
                required_role: string
                    description: "The required role can be filled either by engineers with extensive experience in it or by engineers we'd like to give the opportunity to take it on as a new challenge."
                    label: "required_role"
                    is_required: False
                    is_user_input: False
                    complex_data_type_name: "lightning__textType"
                other_requirements: string
                    description: "The information obtained from the user about their requirements for candidate engineer."
                    label: "other_requirements"
                    is_required: False
                    is_user_input: False
                    complex_data_type_name: "lightning__textType"
            outputs:
                next_question_insight: string
                    description: "A strategic insight or suggested question generated by analyzing the candidate list. It guides the agent on what to ask next to effectively narrow down the candidates (e.g., 'Ask about Department')."
                    label: "next_question_insight"
                    complex_data_type_name: "lightning__textType"
                    is_used_by_planner: True
                    is_displayable: False
                result_count: number
                    description: "The total number of candidate records found matching the criteria."
                    label: "result_count"
                    complex_data_type_name: "lightning__numberType"
                    is_used_by_planner: True
                    is_displayable: False
                selected_candidate_engineers: string
                    description: "A list of candidates that have been finalized or selected as the best match."
                    label: "selected_candidate_engineers"
                    complex_data_type_name: "lightning__textType"
                    is_used_by_planner: True
                    is_displayable: False
            target: "flow://search_resume_and_get_insight"
            label: "Analyze User Requirements"
            require_user_confirmation: False
            include_in_progress_indicator: True
            progress_indicator_message: "Retrieving candidate engineers..."

topic wrap_up_conversation:
    label: "wrap_up conversation"
    description: "If the number of turns in the conversation is too large, forcibly terminate the conversation."
    reasoning:
        instructions: ->
            |   Tell the user the number of turns of the conversation {!@variables.topic_selector_count} and then just say "I've talked too much, haven't I? Shall we bring this to a close?"
        # **日本語訳**
        #   |   これまでの会話ターン数 {!@variables.topic_selector_count} を提示した上で、"お話ししすぎてしまいましたね。そろそろ切り上げましょうか?" と伝えてください。

候補者の検索ロジック

  • 利用している Flow
    • 条件に合致する「社内経歴書」を検索して文字列として結合し、後述の Prompt Template に変数として渡すのみの簡単なフローです。
    • 具体的な仕組みについてはここでは割愛します。

検索ロジックフロー

  • 次の質問への示唆を得る Prompt Template(get_insight_of_next_question)
# 目的と役割
あなたはチームのマネージャーであり、かつ複数プロジェクトのマネージャーも担っています。
適切なプロジェクトアサインを行うために、プロジェクトの要件とエンジニア候補者の経歴書リストを比較し、候補者を絞り込もうとしています。
入力として受け取った情報をもとに、候補者の数をより絞り込むための質問(より詳細な条件を引き出すための質問です)を検討し、その根拠とともに提示することがあなたの目標です。

### 入力情報
##### プロジェクト要件
- 主要プラットフォーム:候補者に経験を求めるプラットフォーム名で、基本的にはプロジェクトにおける主要プラットフォームになります。
<main_platform> {!$Input:main_platform} </main_platform>
- 求められているロール:ユーザーが求めているロールで、このロール経験が豊富なエンジニアや逆にこのロールを新たに挑戦して欲しいエンジニアが候補となります。
<required_role> {!$Input:required_role} </required_role>
- その他の要望:ユーザーから得た、エンジニア候補者に対する要望です。
<other_requirements> {!$Input:unstructured_requirements} </unstructured_requirements>

##### エンジニアの候補者リスト
<candidate_engineers> {!$Input:search_result} </candidate_engineers>

### 出力形式
- 【重要】**出力は必ず英語に翻訳して回答してください。**

<output-sample>
{
 "recommended_question_topic": "The attribute name to ask about (e.g., Department, AWS Experience)",
 "reasoning": "A concise reason why asking this would help narrow down the list",
 "suggested_question_text": "The actual question to ask the user (in English)"
}
</output-sample>
  • 候補者を絞り込む Prompt Template(suggest_candidate_engineers)
# 目的と役割
あなたはチームのマネージャーであり、かつ複数プロジェクトのマネージャーも担っています。
適切なプロジェクトアサインを行うために、プロジェクトの要件とエンジニア候補者の経歴書リストを比較し、候補者を絞り込もうとしています。
入力として受け取った情報をもとに、与えられた候補者の中から最大で3人程度を選出し、その根拠とともに整理することがあなたの目標です。

### 入力情報
##### プロジェクト要件
- 主要プラットフォーム:候補者に経験を求めるプラットフォーム名で、基本的にはプロジェクトにおける主要プラットフォームになります。
<main_platform> {!$Input:main_platform} </main_platform>
- 求められているロール:ユーザーが求めているロールで、このロール経験が豊富なエンジニアや逆にこのロールを新たに挑戦して欲しいエンジニアが候補となります。
<required_role> {!$Input:required_role} </required_role>
- その他の要望:ユーザーから得た、エンジニア候補者に対する要望です。
<other_requirements> {!$Input:unstructured_requirements} </unstructured_requirements>

##### エンジニアの候補者リスト
<candidate_engineers> {!$Input:search_result} </candidate_engineers>

### 出力形式
- 【重要】**出力は必ず英語に翻訳して回答してください。**

<output-format>
{
"selected_candidate_engineers": [
 "engineer_name": "候補者の氏名が入ります(ここは必ずしも英語である必要はありません)",
 "experience_main_platform": "主要プラットフォーム({!$Input:main_platform})の経験に関する説明が入ります(必ず英語)",
 "experience_required_role": "要求されているロール({!$Input:required_role} )の経験に関する説明が入ります(必ず英語)",
 "": "The actual question to ask the user (in English)"
}
</output-format>

<output-sample>
{
 "selected_candidate_engineers": [
 {
  "engineer_name": "Taro Tanaka",
  "experience_main_platform": "Salesforce (Apex, LWC, Flows, Integrations) - 5 years",
  "experience_required_role": "Salesforce Engineer - 5 years",
  "reason_for_selection": "5 years on Salesforce engagements including custom development with Apex and Lightning Web Components, plus integration work (REST/SOAP). Has served not only as an engineer but also as a project manager on multiple projects, demonstrating delivery ownership, stakeholder coordination, and team leadership."
 },
 {
  "engineer_name": "Jiro Sato",
  "experience_main_platform": "AWS (EKS, Lambda, RDS), Terraform, CI/CD - 4 years",
  "experience_required_role": "Backend Engineer - 4 years",
  "reason_for_selection": "Proven backend delivery on AWS: designed and implemented scalable APIs and background jobs, set up Terraform-based infrastructure, and improved release reliability via CI/CD. Comfortable working cross-functionally to clarify requirements and drive implementation from design through production operations."
 }
 ]
}
</output-sample>