2025年4月10日木曜日

GoogleのA2Aのサンプルに含まれるCoderエージェントをOracle APEXから呼び出してみる

先日、GoogleよりアナウンスされたAgent2Agentプロトコル(A2A)のサンプルに含まれているCoderエージェントをOracle APEXのアプリケーションから呼び出してみます。

Googleのアナウンスは以下より参照できます。

A2Aプロトコルのサンプルが含まれているGitHubのリポジトリは以下です。

作業はmacOS Sequoia 15.3.2で実施します。

作成したAPEXアプリケーションは以下のように動作します。最初にAgent Cardを参照し、Agent Cardに含まれるexamplesの1文を取り出し、タスクとしてCoderエージェントを呼び出しています。


最初にサンプルを含むA2Aのリポジトリをクローンしました。

git clone https://github.com/google/A2A

% git clone https://github.com/google/A2A

Cloning into 'A2A'...

remote: Enumerating objects: 320, done.

remote: Counting objects: 100% (90/90), done.

remote: Compressing objects: 100% (71/71), done.

remote: Total 320 (delta 31), reused 29 (delta 18), pack-reused 230 (from 2)

Receiving objects: 100% (320/320), 1.86 MiB | 20.21 MiB/s, done.

Resolving deltas: 100% (73/73), done.

% 


今回はTypeScriptで記述されたサンプルを実行します。サンプルのあるディレクトリへ移動します。

cd A2A/samples/js

% cd A2A/samples/js

js % 


README.mdに記載されてい内容を確認します。マークダウンなのでブラウザからA2A/samples/jsを参照します。


Coderエージェントの実行にはGoogleのAPIキーが必要です。Google AI Studioにアクセスし、API Keyを取得します。



サンプルが依存しているgenkitをインストールします。

npm install genkit

% npm install genkit

npm warn deprecated @types/handlebars@4.1.0: This is a stub types definition. handlebars provides its own type definitions, so you do not need this installed.


added 401 packages, and audited 402 packages in 9s


44 packages are looking for funding

  run `npm fund` for details


found 0 vulnerabilities

js % 


環境変数GEMINI_API_KEYに、先ほどGoogle AI Studioにアクセスして取得したAPIキーを設定します。

export GEMINI_API_KEY=[APIキー]

サンプルのCoderエージェントを実行します。

npm run agents:coder

js % npm run agents:coder


> a2a-samples-js@0.1.0 agents:coder

> npx tsx src/agents/coder/index.ts


[CoderAgent] Server started on http://localhost:41241

[CoderAgent] Press Ctrl+C to stop the server

A2A Server listening on port 41241 at path /



別ウィンドウからCoderエージェントを呼び出すクライアントを起動します。

npm run a2a:cli

js % npm run a2a:cli


> a2a-samples-js@0.1.0 a2a:cli

> npx tsx src/cli.ts


A2A Terminal Client

Agent URL: http://localhost:41241

Attempting to fetch agent card from: http://localhost:41241/.well-known/agent.json

✓ Agent Card Found:

  Name:        Coder Agent

  Description: An agent that generates code based on natural language instructions and streams file outputs.

  Version:     0.0.1

Starting Task ID: fe3dd9e3-0bf2-4710-b2cb-b0032fafe427

Enter messages, or use '/new' to start a new task.

Coder Agent > You: 


Coder Agent > You: に"Write a python function to calculate fibonacci numbers."を入力します。

Artifact Received: fibonacci.pyに続けて、コードが出力されていることが確認できます。

Enter messages, or use '/new' to start a new task.

Coder Agent > You: Write a python function to calculate fibonacci numbers.

Sending...


Coder Agent [11:00:50]: ⏳ Status: working

  Part 1: 📝 Text: Generating code...

Coder Agent > You: 

Coder Agent [11:00:55]: 📄 Artifact Received: fibonacci.py (Index: 0)

  Part 1: 📝 Text: # This file contains a function to calculate Fibonacci numbers iteratively.


def fibonacci(n: int) -> int:

  """

  Calculates the nth Fibonacci number iteratively.


  Args:

    n: The position in the Fibonacci sequence (non-negative integer).


  Returns:

    The nth Fibonacci number. Returns 0 for n <= 0.

  """

  if n <= 0:

    return 0

  elif n == 1:

    return 1

  else:

    a, b = 0, 1

    # Iterate n-1 times because we already have the first number (b=1 for n=1)

    for _ in range(n - 1):

      a, b = b, a + b

    return b


# Example usage (optional):

# print(fibonacci(0))  # Output: 0

# print(fibonacci(1))  # Output: 1

# print(fibonacci(10)) # Output: 55

# print(fibonacci(20)) # Output: 6765



Coder Agent [11:00:55]: ✅ Status: completed

  Part 1: 📝 Text: Generated files: fibonacci.py

SSE stream finished for method tasks/sendSubscribe.

--- End of response for this input ---

Coder Agent > You: 


以上でサンプルのCoderエージェントが実行できました。

このCoderエージェントをOracle APEXのアプリケーションから呼び出してみます。

作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/a2a-talk-to-agent.zip

空のAPEXアプリケーションを作成し、ホーム・ページにすべてを実装しています。

ページ・アイテムP1_TASKに、タスクとして送信するテキストを入力します。


ページ・アイテムP1_RESPONSESend a Taskとして呼び出したレスポンスに含まれる生成結果を表示します。


Agent Cardの内容はページ・アイテムP1_AGENT_CARDに保持します。


サンプルのCoderエージェントは、起動時にJSONRPCによるリクエストを受け付けるURLを表示します。

% npm run agents:coder


> a2a-samples-js@0.1.0 agents:coder

> npx tsx src/agents/coder/index.ts


[CoderAgent] Server started on http://localhost:41241


今回はAPEXをpodmanのコンテナで実行しているため、localhostへのアクセスにはhost.containers.internalを代わりに指定する必要があります。

このURLは作業環境で異なるため、アプリケーション定義置換に、置換文字列G_ENDPOINTとして設定します。


ボタンAGENT_CARDをクリックしてAgent Cardを取得します。Agent Cardを取得するプロセスAgent Cardに、以下のコードを記述します。

/*
* Agent Card
* https://google.github.io/A2A/#/documentation?id=agent-card-1
*/
declare
l_url varchar2(200);
l_response clob;
e_api_call_failed exception;
begin
-- Agent CardのURL
l_url := :G_ENDPOINT || '/.well-known/agent.json';
apex_debug.info('endpoint = %s', l_url);
apex_web_service.set_request_headers('Content-Type', 'application/json');
l_response := apex_web_service.make_rest_request(
p_url => l_url
,p_http_method => 'GET'
);
if apex_web_service.g_status_code <> 200 then
raise e_api_call_failed;
end if;
select json_serialize(l_response returning clob pretty) into :P1_AGENT_CARD;
end;
view raw agent-card.sql hosted with ❤ by GitHub


ボタンSEND_A_TASKをクリックしたときに、タスクの実行をCoderエージェントに依頼します。タスクの実行を依頼するプロセスSend a Taskに、以下のコードを記述します。

declare
l_url varchar2(200);
/*
* tasks/send
*/
l_request clob;
l_task json_object_t;
l_id number;
l_params json_object_t;
l_params_id varchar2(32);
l_guid varchar2(36);
l_message json_object_t;
l_parts json_array_t;
l_part json_object_t;
e_api_call_failed exception;
/*
* response
*/
l_response clob;
l_response_json json_object_t;
l_result json_object_t;
l_artifacts json_array_t;
l_artifact json_object_t;
/*
* GUIDを3262b5fd-c7fa-faa8-e063-0200590aca0eの形式で取得する。
*/
function get_guid return varchar2
as
l_guid varchar2(32);
l_formatted_guid varchar2(36);
begin
l_guid := lower(rawtohex(sys_guid()));
l_formatted_guid := apex_string.format(
'%s-%s-%s-%s-%s'
,substr(l_guid, 1, 8)
,substr(l_guid, 9, 4)
,substr(l_guid, 13, 4)
,substr(l_guid, 17, 4)
,substr(l_guid, 21)
);
return l_formatted_guid;
end get_guid;
begin
l_url := :G_ENDPOINT;
apex_debug.info('endpoint = %s', l_url);
/*
* Send a Task
* https://google.github.io/A2A/#/documentation?id=send-a-task
*/
l_task := json_object_t();
l_id := 1;
l_task.put('jsonrpc','2.0');
l_task.put('id', l_id);
l_task.put('method', 'tasks/send');
l_params := json_object_t();
l_guid := get_guid();
l_params.put('id', l_guid);
l_message := json_object_t();
l_message.put('role', 'user');
l_parts := json_array_t();
l_part := json_object_t();
l_part.put('type', 'text');
l_part.put('text', :P1_TASK);
l_parts.append(l_part);
l_message.put('parts', l_parts); -- Send a Taskの例にはdataとなっているがpartsが正しい。
l_params.put('message', l_message);
l_params.put('metadata', json_object_t()); -- empty object
l_task.put('params', l_params);
l_request := l_task.to_clob();
apex_debug.info(l_request);
apex_web_service.set_request_headers('Content-Type', 'application/json');
l_response := apex_web_service.make_rest_request(
p_url => l_url
,p_http_method => 'POST'
,p_body => l_request
);
if apex_web_service.g_status_code <> 200 then
raise e_api_call_failed;
end if;
/*
* responseからartifactを取り出す。
*/
l_response_json := json_object_t(l_response);
l_result := l_response_json.get_object('result');
l_artifacts := l_result.get_array('artifacts');
apex_debug.info('artfifacts = %s', l_artifacts.to_clob());
-- 今は最初のartifactだけを取り出す。
l_artifact := treat(l_artifacts.get(0) as json_object_t);
l_parts := l_artifact.get_array('parts');
apex_debug.info('parts in first artifact = %s', l_parts.to_clob());
-- 最初のpartを取り出す。
l_part := treat(l_parts.get(0) as json_object_t);
apex_debug.info('first part in first artifact = %s', l_part.to_clob());
-- partはtext決め打ちで取り出す。
:P1_RESPONSE := l_part.get_string('text');
end;
view raw send-a-task.sql hosted with ❤ by GitHub


レスポンスに含まれる配列artifactsの先頭の要素を参照し、さらに、artifactに含まれる配列partsの先頭の要素だけを参照しているため、配列artifactsやpartsの要素が複数あると、生成結果を正しく受け取れないことがあります。今回はとりあえずA2Aのサンプルを呼び出すことが目的なので、そのままにしています。

以下のようにCoderエージェントのログにEmitting final file (index 1)と表示されている生成結果は、(indexが0でないため)APEXアプリケーションには表示されません。

[CoderAgent] Server started on http://localhost:41241

[CoderAgent] Press Ctrl+C to stop the server

A2A Server listening on port 41241 at path /

[[Task 3262b5fd-c805-faa8-e063-0200590aca0e] Created new task and history.

[CoderAgent] Emitting completed file (index 0): fib

[CoderAgent] Emitting final file (index 1): fibonacci.py

[Task 3262b5fd-c807-faa8-e063-0200590aca0e] Created new task and history.

[CoderAgent] Emitting final file (index 0): fibonacci.py

[Task 3262b5fd-c80a-faa8-e063-0200590aca0e] Created new task and history.

[CoderAgent] Emitting final file (index 0): fibonacci.py



今回の記事は以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。