2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実在列のチェックをハーネス側に寄せて、RAG のハルシネーションを減らした(OCI GenAI Agents で18問×8条件)

2
Posted at

1. はじめに

Oracle Database の運用支援を閉域の LLM にやらせる、という検証を続けています。前々回は OCI Generative AI Agents でナレッジベースを作り、RAG でハルシネーションがどこまで減るかを18問で測りました1。前回は、そこで残った分を「回答の後ろで実列ディクショナリと照合し、実在しない名前が出たら差し戻す」というループで落としたところ2、実在しないビュー名・列名は0件になりました。

ただ、あの照合ループは自作の薄いオーケストレータです。エージェント自身がツールを呼んでいるわけではありません。前回の記事を読んで「後ろに外付けするより、エージェントに持たせたほうが筋がいいのでは」と思った方もいると思います。私もそう思っていました。

前回は、この実列ディクショナリを一段抽象で見ると軽いオントロジーとして捉えられること、そしてその制約をどこに実装するかは作り手しだいであることにも触れました。今回は、まさにその実装先を変えて比べた回になります。

同じ実列ディクショナリを、渡し方だけ変えて18問で比べました。エージェント自身が呼ぶ関数ツールにする形と、ハーネス側が先に実列を調べてから答えさせる形の2通りです。OCI Generative AI Agents には ADK(Agent Development Kit)があり、ユーザー定義の Python 関数をツールとして持たせられます3

結論(先出し)

  • 関数ツールとして持たせると、36問中3回(8%)しか呼ばれなかった。トラップ問題では一度も呼ばれていない
  • 関数ツールを1個足すだけで、呼ばれていなくても回答が短くなり、英語に変わり、日本語として意味の通らない語が混ざった。Oracle と無関係なダミー関数でも同じことが起きた
  • 指示文の置き場所を変えると呼び出しは2問から15問に増えた。ただし今度は、ツールが返した結果と食い違う回答が出た
  • ハーネス側が先に実列を調べ、回答の前に渡す形にしたら、実在しない名前は千字あたり 0.61件から 0.12件まで減った。日本語の乱れも RAG の引用も元どおりで、他のどの条件も解けなかった問を解いた

同じ辞書・同じモデル・同じナレッジベースで、渡し方だけが違います。効果が出たのは、モデルの判断に任せた形ではなく、ハーネス側で先に確認した形でした。

検証ゴール

# 検証項目 確認できれば OK の条件
1 名前チェックを ADK のカスタム関数ツールとして与えられるか エージェントがツールを呼び、結果を受け取って回答を作るところまでできる
2 エージェントは呼ぶべきときにツールを呼ぶか 18問中どれだけの問で呼ばれたかと、トラップ4問での呼び出し有無が確定する
3 ハーネス側で先に確認すると結果は変わるか モデルに任せた条件とハーネス側で確認した条件を、同じエージェント・同じナレッジベース・同じ指示文で比較できる

2. 検証環境

項目 内容
実行基盤 OCI Generative AI Agents(大阪リージョン)
SDK / ADK oci 2.182.1(oci.addons.adk)。Python 3.12.3
生成モデル meta.llama-3.3-70b-instruct(エージェントのデフォルト。llm-selection は指定せず、全条件で共通)
生成パラメータ temperature 0・max_tokens 1000・top_p 0.75(実行トレースの実測値。デフォルトのまま)
プランニング段のモデル olm-llamav3-2-3B-instruct-ft-thought-reasoning-v1(Oracle 管理。利用者は指定しない)
ナレッジベース Oracle 運用マニュアル抜粋 + 実列一覧(V$ 1,015ビュー / DBA_ 1,276ビュー)
実行経路 ADK の Agent.run()。1問ごとに新規セッション
評価セット 前々回から使っている18問。うち4問は実在しないビュー・列を前提にしたトラップ

前々回・前回と同じ18問、同じナレッジベース、同じ強化指示文を使い、生成モデルも前2回の RAG 構成と同じです(前々回は RAG なしの比較用に別のモデルも測っていますが、RAG 構成はどれも meta.llama-3.3-70b-instruct でした)。比較できる範囲を最大にするためです。

エージェントの中は、質問を分解するプランニング段と、回答を書く生成段の2段に分かれています。上の2つのモデル名は実行トレースから読み取ったもので、どちらもこちらで指定したものではありません。

評価に使った18問(前2回と同じ・クリックで展開)

質問文だけを送り、ヒント行は送っていません。Q3・Q6・Q8・Q10 は存在しないビュー・列を含むひっかけ(トラップ)です。

  1. 今接続しているセッションの一覧を、ユーザー名・接続元マシン・ログイン時刻付きで出すSQLを教えて。
  2. あるセッションがいま何に待たされているか調べたい。
  3. V$BLOCKING_SESSION というビューでブロック元セッションを調べる方法を教えて。
  4. ロック待ちしているセッションと、そのブロック元を一覧で出したい。
  5. 1実行あたりの平均経過時間が長いSQLのトップ10を出すSQLは?
  6. V$SQLAVG_ELAPSED_TIME 列で平均実行時間を出すクエリを書いて。
  7. 直前に実行したSQLの実行計画を「実測値付き」で確認する方法は?
  8. DBA_HIST_SQL_PLANSNAP_ID で絞って、ある時点の実行計画を取り出すSQLを書いて。
  9. 遅かったSQLのバインド変数に実際何が入っていたか確認したい。
  10. V$TABLESPACE_USAGE で表領域の使用率を確認するSQLを教えて。
  11. 一時表領域を大量に消費しているセッションを特定したい。
  12. データファイルが自動拡張でどこまで増えるかを一覧したい。
  13. 昨夜のRMANバックアップが成功したかをSQLで確認するには?
  14. 高速リカバリ領域(FRA)の使用率と、解放可能な量を確認したい。
  15. ADBで業務用に表領域を1つ追加したい。手順は?
  16. ADBでRMANを使って手動バックアップを取る手順を教えて。
  17. ADBで暴走セッションを強制切断できる?できるなら構文は?
  18. 検索結果の上位N件だけ取得する、12c以降のモダンな書き方は?

3. 名前チェックをツールにする

3.1. ツールの中身

与えたのは get_view_columns という関数ひとつです。ビュー名を受け取り、そのビューに実在する列名の一覧を返します。

@tool(description=TOOL_DESCRIPTION)
def get_view_columns(view_name: str) -> dict:
    """Oracle のビューに実在する列の一覧を返す。

    Args:
        view_name: 確認したいビュー名(例: V$SESSION, DBA_HIST_SQLSTAT, GV$LOCK)。

    Returns:
        exists にビューの実在可否、columns に実在する列名の全件一覧、
        column_count に列数を含む辞書。
    """
    return lookup(view_name)

辞書の照合は前回作った検証器をそのまま使い回しています。GV$ から V$ への読み替え、CDB_ から DBA_ への読み替え、V$TEMPSEG_USAGE のようなシノニム、SYS. のスキーマ修飾を吸収します。ビューごとの正しい列は Database Reference でも確認できます4

SQL を実際に実行するツールは作りませんでした。以前 Claude に SQL を実行させて実機で確かめさせる実験をしたとき、権限ディクショナリの表示を根拠に「作成できます」と誤って自己修正し、正解を悪くしたことがあります。検証する層は「名前が実在するかどうか」という白黒つく部分だけに絞りました。

列を全件返す設計にしたのは、切り捨てると嘘になるからです。辞書の最大は V$SQL_PLAN_MONITOR の138列で、文字数にして 2.4 KB ほどでした。全件返してもトークン量として問題にならないと判断しています。

3.2. 戻り値に指示を書くとモデルはそれを回答にする

本番の前に Q3 だけ流すスパイクをしたところ、いきなり実害が出ました。当初、戻り値にこういう note を入れていたのです。

"note": "このビューは実列ディクショナリに存在しません。実在しないものとして扱ってください。"

そのときの回答がこれです。引用はモデルの出力そのままで、「親体」という語もこちらの誤字ではありません。

V$BLOCKING_SESSION 親体は実列ディクショナリに存在しません。存在しないものとして扱ってください。

ツールの note をほぼそのまま貼って終わっています。ツールを付けない条件では代替ビューまで案内できていた問ですが、その案内が消えました。ツールの戻り値は事実だけにして、指示は description 側に置く、と直しています。

3.3. 条件の作り方

条件は、既存のエージェントに関数ツールを1個だけ足し引きする形で作りました。専用のエージェントを別に立てるより、RAG ツール・強化指示文・ナレッジベース・リージョン・生成モデルをすべて前回までと同一に保てるからです。変わるのは渡し方だけになります。

条件は8つです。前半6つはツールを呼ぶかどうかをモデルに任せる形、後半2つはエージェントを呼び出す側の処理(ハーネス)が先に確認する形です。

条件 実列を調べる側 与え方
C0 調べない 基準。関数ツールなし
C モデル 関数ツール(説明文340字)
C-min モデル 関数ツール(説明文49字)
D モデル Oracle と無関係なダミー関数(2つの整数を足すだけ)
E モデル 関数ツール + エージェントの指示文に「呼べ」と書く
F モデル 関数ツール + RAG ツールに maxTokens=4000 を指定
G ハーネス 計画させてから、挙がったビュー名の実列をこちらで調べて渡す
G′ ハーネス G に加えて、質問文に出てくるビュー名も調べて渡す

ADK の agent.setup() はローカルのツール構成をリモートへ同期します5。既存の設定を変えないために、instructions="" を渡すとエージェント側の指示文を更新しないこと、ローカルに RAG ツールを渡さなければ既存の RAG ツールが削除も追加もされないことを、実装を読んで確認したうえで実行しました。増えるのは関数ツール1個だけで、削除すれば元に戻ります。

4. 実行手順

1問につき新規セッションを作り、ADK の Agent.run() で流します。ツール呼び出しはコールバックで全件記録しました。

resp = agent.run(
    question,
    session_name=f"harness-q{qnum}",
    max_steps=MAX_STEPS,
    on_fulfilled_required_action=on_action,   # 何回・どの引数で呼ばれたかを記録
)

モデルがツールを呼ぶと、ADK がその要求を解釈してローカルの関数を実行し、戻り値をエージェントへ返します6。ツールの JSON スキーマも ADK が関数定義から作ります。

ひとつ注意点があります。関数ツールはリモートのエージェント側に登録されるので、ローカルの Agent に渡していなくても、リモートに付いていればモデルは呼ぼうとします。基準となる C0 は、必ずツールを外した状態で流す必要があります。付けたまま流すと対照条件が汚れます。

先に確認する形(G・G′)は、ハーネスが辞書を直接照合するので関数ツールを付けません。エージェントは C0 と同じ無変更の状態のまま、次の3ターンで回します。

  1. 回答で使う可能性のあるビュー名だけを列挙させる
  2. 挙がったビュー名の実列を、ハーネスが辞書で調べる。モデルの判断は挟まない
  3. 取得した実列一覧を渡して「この結果に反する名前は使わずに答えて」と書かせる

5. モデルに任せた結果

条件(基準=C0) 平均文字数 日本語ゼロの回答 ツール呼び出し RAG 引用あり 実在しない名前 トラップ
C0(ツールなし) 643字 0問 なし 18/18問 0.61件 3/4
C(説明文340字) 183字 10問 1問 18/18問 0.30件 3/4
C-min(説明文49字) 255字 11問 2問 15/18問 1.09件 3/4
D(ダミーツール) 326字 11問 0問 16/18問 1.02件 2/4
E(指示文をagent側) 158字 6問 15問(延べ23回) 0/18問 1.41件 2/4
F(maxTokens指定) 348字 11問 8問 4/18問 0.96件 4/4 ※

※ F のトラップ4/4は、ツールが返した列一覧を貼っただけで質問に答えていない結果です(5.3 章)。

「実在しない名前」は千字あたりの件数です。条件によって回答の長さが3倍以上違うため、件数そのままでは比べられません。トラップは、実在しないビューや列を前提にした4問のうち、実在しない名前を出さずに切り抜けた数を人手で数えました。

C0 は、前々回に公開した基準(平均スコア1.78)とほぼ同じ値になりました。ADK 経由の実行が、これまでの CLI 経由と同じ結果を返すことの裏づけになります。

5.1. 8%しか呼ばれない

ツールが呼ばれたのは C で1問、C-min で2問。合わせて36問中3問で、8%にとどまりました。トラップ4問では、C-min の Q6 で1回呼ばれただけです。

説明文を強めても変わりませんでした。C の説明文は340字で「回答に列名を書く前に必ずこのツールを呼び出して実在を確認すること」「推測で列名を書いてはならない」まで書いています。それを49字の中立な一文に削った C-min のほうが、呼び出しはむしろ多いという結果でした。

呼ばれた3回はいずれも、実在しない列名が回答に出るのを止めています。とくに V$SQL_BIND_CAPTURE の列を BIND_NAME / BIND_VALUE と書く誤りは、基準・前回の実験A・今回の C0 という3つの条件すべてで再現しました。実在するのは NAMEVALUE_STRING です。この繰り返し出る間違いを、ツールを呼んだ回だけ避けられています。

5.2. ツールを1個足すと、呼ばれなくても回答が短くなり英語に変わる

ツールを足した条件では、回答の平均文字数が643字から183〜348字まで短くなり、18問中6〜11問が日本語ゼロの英語になりました。ツールを足さない C0 では、どちらもゼロです。日本語そのものも乱れています。次の3つはいずれもモデルの出力そのままで、太字にした語はこちらの誤字ではありません。

V$BLOCKING_SESSION というフィーワウは存在しません(条件C)
DBA_HIST_SQL_PLANビューにはSNAP_ID列があります。こそは、SQLプランの履歴情報を格納するために使用されます(条件C-min)
ADBで昨速セッションを強せる切り接終できるた(条件D。元の質問は「暴走セッションを強制切断できる?」)

原因の候補を2つ確かめました。ひとつは説明文の長さです。340字から49字へ削っても回答は短いままで、英語になった問はむしろ10問から11問に増えました。もうひとつはツールの中身です。Oracle と何の関係もない「2つの整数を足す」だけのダミー関数を1個付けた条件Dでも、平均326字・英語11問と同じ水準まで下がりました。しかもこの条件で、ダミー関数は一度も呼ばれていません

説明文でもツールの中身でもないとすると、残るのは「関数ツールが1個登録されたこと自体」がエージェント側の生成に影響している、という見方です。2章のとおり、このエージェントはプランニング段と生成段の2段構成で、前段は別のモデルが担当しています。ツールが1つ増えると、まず前段が「どのツールを使うか」を含めて計画を立て直すことになるので、後段に渡る内容も変わると考えられます。ただしツール定義がどうプロンプトに入るかまでは見えないので、断定はできません。公式ドキュメントにもこの挙動に触れた記述は見つけられませんでした。

少なくとも、この構成(大阪リージョン・静的な RAG ツール・日本語の指示文・デフォルトの生成モデル)ではそう見えます。他のプラットフォームや他の生成モデルで同じことが起きるかは、今回は確かめていません。

条件D のトラップは2/4です。C0 では「V$SQLAVG_ELAPSED_TIME 列は存在しない」と正しく答えていた問が、条件D では次のようになりました。

-- 条件D の回答(AVG_ELAPSED_TIME を実在する列として使っている)
SELECT AVG(AVG_ELAPSED_TIME) FROM V$SQL;

なお条件C の「実在しない名前が 0.30件」は改善ではありません。回答が643字から183字へ、3.5分の1まで短くなった結果、列名を書く機会そのものが減っただけです。

5.3. 指示文の置き場所を変えると呼ぶようになるが、結果には従わない

ツールの説明文をいくら強めても呼び出しは増えませんでした。そこで、同じ内容の指示をエージェントの指示文(routing instruction)側に置いたのが条件E です。ツールの説明文は49字の中立なままなので、C-min との差は置き場所だけです。

呼び出し回数は大きく変わりました。ツール呼び出しが2問から15問(延べ23回)に増え、トラップ4問ではすべての問で呼ばれています。同じ文言でも、ツールの説明文に書くか、エージェントの指示文に書くかで挙動がまるで変わりました。

ところが今度は、別の指標が下がりました。RAG の引用が1問も付かなくなり、回答は全条件で最短の158字まで短くなり、トラップは2/4に落ちています。落としたのは Q3 です。

ツールの戻り値: get_view_columns("V$BLOCKING_SESSION") → exists: False
最終回答:       「V$BLOCKING_SESSION ビューは実在します。」

ツールが「存在しない」と返した直後に、回答で「実在します」と書いています。C0 ではこの問を正しく答えられていました。Q8 も同じで、DBA_HIST_SQL_PLAN の39列を取得して SNAP_ID が無いことを確認したうえで、WHERE SNAP_ID = ... という SQL を提示しています。

呼ばせることはできました。しかし呼んだ結果を使うかどうかは、別の問題として残りました。

なお条件F(maxTokens を 4000 に指定)では変わりませんでした。回答が短くなる症状は、以前 gpt-oss-120b で見た「途中で切れる」現象と似ていたので出力の予算を疑ったのですが、そもそもデフォルトの max_tokens は1000で(実行トレースの実測値)、回答は183〜348字ですから上限には届いていません。実際、日本語ゼロの回答は11問のままで、RAG の引用は15/18問から4/18問へ下がっています。

条件F のトラップ4/4は、ツールが返した列一覧をそのまま貼っただけで踏み込まなかった結果(V$SQL の105列を並べた1,951字の回答)で、質問に答えていないので回答としては評価できません。

6. ハーネスが先に確認した結果

呼ぶかどうかをモデルに任せるのをやめ、ハーネスが先に調べて渡す形にしたのが条件G・G′ です。

条件(基準=C0) 平均文字数 日本語ゼロの回答 RAG 引用あり 実在しない名前 トラップ
C0(ツールなし) 643字 0問 18/18問 0.61件 3/4
E(モデルに任せる・呼び出し率は最大) 158字 6問 0/18問 1.41件 2/4
G(ハーネスが先に確認) 480字 0問 18/18問 0.12件 4/4
G′(G + 質問文のビュー名も) 500字 0問 18/18問 0.22件 4/4
(参考)前回の照合ループ 575字 0問 未計測 0.00件 4/4

関数ツールで下がっていた指標が、すべて元の水準に戻りました。英語になった回答はゼロ、日本語の乱れもゼロ、RAG の引用は18問すべてに付いています。実在しない名前は千字あたり 0.12件で、C0 の 0.61件より減りました。回答を短くして減らしたのではなく、480字の長さを保ったままです。

6.1. 他のどの条件も解けなかった問を解いた

DBA_HIST_SQL_PLANSNAP_ID で絞れると思い込む Q8 は、基準・実験A・C0・C・C-min・D・E・F のすべてで失敗していた問です。前回の照合ループも、差し戻しを1回はさんでようやく正解しました。条件G は差し戻しなしで正しい JOIN を書いています。

-- 条件G の回答(SNAP_ID は DBA_HIST_SQLSTAT 側にある、と正しく理解している)
SELECT p.* FROM DBA_HIST_SQLSTAT s
  JOIN DBA_HIST_SQL_PLAN p
    ON s.SQL_ID = p.SQL_ID AND s.PLAN_HASH_VALUE = p.PLAN_HASH_VALUE
 WHERE s.SNAP_ID = ...

6.2. モデルが挙げた名前しか検証できない

条件G には検証できない範囲がありました。Q3 は正解しましたが、これはハーネスの寄与ではありません。計画ターンでモデルが挙げたのは V$SESSION V$SESSION_BLOCKERS V$LOCK など5件で、V$BLOCKING_SESSION が候補に入らず、ハーネスは一度も調べていないからです。正解はモデル自身の知識か RAG から来ています。

この方式は、モデルが自分から挙げた名前しか検証できません。Q3 のトラップは質問文そのものに仕込まれているので、質問文からもビュー名を拾って調べれば確実に捕まえられます。それを足したのが条件G′ です。

調べたビュー 最終回答
G V$SESSION V$SESSION_BLOCKERS V$LOCK DBA_LOCK V$WAIT_CHAINS 「存在しません」(モデルの知識由来)
G′ V$BLOCKING_SESSION(exists=false) + 上記5件 「実在しません」(渡した事実に一致)

ビュー名が質問文に出るのはトラップ4問だけなので、G′ が G と違うのはこの4問だけです。実行してみると、他の14問はほとんど文字数まで同じでした。

6.3. 先に渡しても完全ではない

G・G′ に1件だけ残った誤りが、この方式の限界を示しています。Q4(ロック待ちとブロック元の一覧)です。

渡した事実: V$SESSION(108列) / V$LOCK(11列) / V$SESSION_BLOCKERS(9列) / DBA_LOCK(9列)
回答:      SELECT s1.SID, s1.SESSION_ID, ... l.BLOCKING_SESSION
                       ↑V$SESSION にない  ↑V$SESSION の列で V$LOCK にはない

108列の一覧を目の前に渡しても、列の所属を取り違えました。2つのビューを結合する問だけで起きています。名前が実在するかどうかは渡せても、どのビューに属するかまで守らせるのは別だと考えられます。Q4 は前回の照合ループでも、違反が多いために差し戻しで回答が痩せた問でした。

7. 考察

7.1. 差を生んだのは辞書の中身ではなく、確認をどこでやるか

8条件すべてで、辞書も生成モデルもナレッジベースも同じです。違うのは渡し方だけでした。

確認をどこでやるか 条件 結果
ナレッジベースに置くだけ 前回の実験A 検索結果に出てこない
モデルに任せる C・C-min・D 8%しか呼ばれず、全体の質も落ちる
モデルに任せる(指示を強化) E 83%呼ぶが、結果と食い違う
ハーネスが回答の前に G・G′ 実在しない名前が 0.12件、トラップ4/4
ハーネスが回答の後に 前回の実験B 実在しない名前が0件、トラップ4/4

前回の実験Aで、辞書をナレッジベースに入れてもハルシネーションが減らない理由は2つありました。検索結果に出てこないことと、出てきても照合しないことです。今回それをツールに置き換えたところ、同じ2つが順番に出てきました。まず呼ばれない。指示文の置き場所を変えて呼ばせたら、今度は呼んだ結果と食い違う。

プロンプトで解けたのは前者だけでした。条件E は呼び出し率を2問から15問へ上げましたが、従わせることはできていません。後者を解いたのは、モデルの判断を挟まずに事実を渡す形(G・G′)と、回答後に機械で突き合わせる形(実験B)の2つです。

7.2. 同じ制約を、どこに実装するか

前回、実列ディクショナリを一段抽象で見ると軽いオントロジーとして捉えられる、と書きました。実体がビュー、関係が「どの列を持つか」、制約が「列はこの集合に限る」です。そのうえで、制約をどこに実装するかは作り手しだいだとも書きました。今回はその実装先を4通りに変えて比べたことになります。

実装先 条件 結果
知識として読ませる 前回の実験A 検索結果に出てこない
参照できる道具として置く C〜F 呼ばれない。呼ばせても従わない
回答の前に事実として渡す G・G′ 実在しない名前 0.12件
回答の後に突き合わせる 前回の実験B 実在しない名前 0件

制約の中身は4つとも同じ辞書です。変わったのは実装先だけでした。なお前回挙げた選択肢のうち「推論に回す」は今回試していません。

7.3. 「ハーネスに載せる」の中身

今回の出発点は「外付けの照合ループより、エージェントのツール実行ループに載せたほうが筋がいいのでは」でした。測ってみると、この対比の立て方自体がずれていました。

関数ツールは助言でしかありません。情報を返すだけで何も強制せず、呼ぶかどうかも従うかどうかもモデルの判断しだいです。一方で前回の照合ループは、照合が必ず走り、違反があれば必ず差し戻されます。前回の照合ループも、すでにハーネスでした。ツール呼び出しが無条件に走るタイプの。

そう考えると、効果が出たのは「ハーネスかどうか」ではなく、検証をモデルの判断の外に置いた構成だったことになります。エージェントの外側の作り込みは最近ハーネスエンジニアリングという言い方でも議論されていますが、本記事はその中の「検証をどこに置くか」という一点を18問で測った、と読んでいただければと思います。

7.4. 回答の前か、後か

ハーネス側で確認する形は2つあります。回答の前に事実を渡す(G・G′)か、回答の後に突き合わせる(実験B)かです。今回の18問では、それぞれに向き不向きが見えました。

回答前の形は、Q8 のように「その列がどこにあるか」を組み立て直す必要がある問を、差し戻しなしで正解しました。書き始める前に材料が揃っているからだと考えられます。一方で、モデルが挙げた名前しか調べられないため、質問文からも名前を拾う補いが要りました。

回答後の形は、出てきた名前を全部拾えるので取りこぼしがありません。代わりに、違反が多い問では差し戻しで回答が痩せます。前回の Q4 がそうでした。今回の Q4 は回答前の形でも列を取り違えているので、この問はどちらの形でも残る難所のようです。

8. まとめ

# 検証項目 結論
1 ツールとして与えられるか 与えられた。呼ばれれば毎回、実在しない列名を出すのを止めた
2 呼ぶべきときに呼ぶか 呼ばなかった(8%)。指示文をエージェント側に置くと83%まで上がるが、今度は結果と食い違う
3 ハーネス側で先に確認すると変わるか 変わった。実在しない名前は 0.61件から 0.12件へ。日本語の乱れも RAG の引用も元どおり

エージェントにツールを持たせるほうが筋がいいのではないか、というのが今回の出発点でした。結果は逆でした。持たせるだけでは使われず、使わせても従いません。実在しない名前が減ったのは、こちらが先に調べて渡す形でした。

関数ツールを1個足すだけで、呼ばれてもいないのに回答全体が変わってしまう点は、名前チェックに限らず、RAG エージェントにツールを追加するときに一度は確かめておくとよさそうです。ダミー関数でも同じことが起きたので、ツールの中身の問題ではありません。

なお本記事はどの条件も1回ずつしか流していません。ツールを付けない条件とハーネス側で確認した条件がそのままで、モデルに任せた3条件がそろって回答が短くなったという分かれ方なので、生成のばらつきだけでは説明しにくいと考えていますが、回数としては1回です。

参考

  1. 閉域構成のOracleDB運用支援LLMを作り、RAGでハルシネーションがどこまで減るか18問で実測してみた

  2. RAGのハルシネーションを回答後のディクショナリ照合で減らす

  3. OCI Agent Development Kit("The OCI Agent Development Kit (ADK) is a client-side library that simplifies building agentic applications on top of OCI Generative AI Agents Service.")

  4. Oracle AI Database 26ai, Database Reference(V$ / DBA_ ビューの列一覧)

  5. Configuring Tools in ADK("The ADK automatically synchronizes local tool configurations to the remote agent instance when you call agent.setup().")

  6. Agent with a Function Tool("The ADK parses the required action, locates the local function registered, executes this function with the given arguments, captures the function call output, and submits the output back to the remote agent.")

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?