引用つきの回答
検索したチャンクに番号を付けてモデルに渡し、一文ごとに出典を明記すること、資料にないなら「ない」と言うことを求めます。さらにプログラムで引用が有効かをチェックし、ドキュメントに答えのない質問でモデルがでっち上げるかどうかも見ます。
- 約 35 分
- 難易度:中級
- 検証:2026-09-14 deepseek-flash、multilingual-e5-small
コードと実行結果は実際に動かしたときのまま載せているため、コメントと出力は中国語です。
検索がうまくいったら、見つけた資料をモデルに渡す必要があります。どう渡すか、渡した後に何を求めるかで、最終的な回答が信頼できるかどうかが決まります。
この課の目標は三つです。モデルが資料だけに基づいて答えること、一文ごとにどの資料に基づいているかを明記すること、資料に答えがないときは正直に「ない」と言うこと。そして、それができているかを自動でチェックするプログラムを書きます。
資料に番号を付ける
検索には前の課で最もよい結果だった「書き換え + ハイブリッド RRF」を使い、上位 5 個のチャンクを取ります。各チャンクを、番号と出典の付いたタグで包みます。
def build_context(results):
parts = []
for i, (_, file, text) in enumerate(results, 1):
parts.append(f'<doc id="{i}" source="{file}">\n{text}\n</doc>')
return "<docs>\n" + "\n".join(parts) + "\n</docs>"
プロンプトに入れるとこうなります(内容は省略しています)。
<docs>
<doc id="1" source="compatibility.md">
## Redirects
Unlike `requests`, HTTPX does **not follow redirects by default**. ……
</doc>
<doc id="2" source="compatibility.md">
……
</doc>
……
</docs>
问题:httpx 默认会自动跟随重定向吗?怎么开启?
番号はモデルが引用するためのもので、source はモデルが参照するため、そして最後にプログラムがユーザーに見せるためのものです。モジュール 02 第 1 課で、タグで素材と指示を分けることを扱いました。ここの <doc> タグにはもう一つ役割があります。資料の各一節の境界をはっきり示すので、モデルが二つの一節を一つに混ぜてしまうことがありません。
プロンプト
SYSTEM = """你是 httpx 的答疑助手。只根据 <docs> 里的文档片段回答用户的问题。
规则:
- 每句话后面用 [编号] 注明依据的是哪个片段,可以同时引用多个,如 [1][3]。
- 片段里没有的信息不要写,哪怕你自己知道。
- 如果这些片段不足以回答问题,就直接说"文档里没有找到相关说明",不要猜。
- 用中文回答,简洁,代码保持原样。"""
二つ目のルールの「自分で知っていても」という部分が重要です。モデルはもともと httpx について多くを知っていますが、その知識は間違っているかもしれません。たとえば既定でリダイレクトに従う、というように。資料だけを使うよう求めるのは、モデルの記憶の正しさを、資料の正しさで置き換えることです。代償として、資料にたまたま書かれていないけれどモデルは実は知っていることも、言えなくなります。第 7 課でそういう例に出会います。
三つ目のルールは、モデルにはっきりした「逃げ道」を与えています。これがないと、資料が足りないときモデルは自分の知識で埋め合わせがちで、それこそ私たちが避けたいことです。
実行結果
質問を 4 つ用意しました。ドキュメントにはっきりした答えがあるものが二つ。ドキュメントでまったく触れられていないものが一つ(HTTP/3。httpx のドキュメントをすべて検索しましたが、一度も出てきません)。そして答えを出すには資料をよく読む必要があるものが一つ(httpx には組み込みのリトライがあるか)。完全なコードは code/04-rag/citations.py にあります。実行結果です。
问:httpx 默认会自动跟随重定向吗?怎么开启?
检索到:[1] compatibility.md [2] compatibility.md [3] quickstart.md [4] http2.md [5] compatibility.md
答:httpx 默认**不会**自动跟随重定向,所有 HTTP 方法都是如此 [1][3]。
你可以在单个请求上显式开启 [1][3]:
```python
response = client.get(url, follow_redirects=True)
```
或者在创建 `Client` 时就默认开启 [1]:
```python
client = httpx.Client(follow_redirects=True)
```
开启后,可以通过 `response.history` 属性查看被跟随的重定向响应列表,按发生顺序排列 [3]。
检查:引用了 [1, 3],不存在的编号 无,没有引用的句子 0 句(输入 1002 词元)
问:httpx 支持 HTTP/3 吗?
检索到:[1] async.md [2] http2.md [3] advanced/transports.md [4] http2.md [5] advanced/transports.md
答:文档里没有找到相关说明。
检查:引用了 [],不存在的编号 无,没有引用的句子 1 句(输入 1128 词元)
未引用:文档里没有找到相关说明。
问:httpx 自带请求失败自动重试的功能吗?
检索到:[1] third_party_packages.md [2] advanced/transports.md [3] compatibility.md [4] async.md [5] advanced/transports.md
答:httpx 自带连接层面的重试功能,通过直接实例化 transport 来使用:当发生 `httpx.ConnectError` 或 `httpx.ConnectTimeout` 时,请求会按给定的次数重试,从而在链路不稳定的网络下更平滑地运行 [2]。
(代码示例省略)
如果只需要专门的 HTTPX 重试层,可以看 httpx-retries,它是一个 for HTTPX 的 retry layer [1]。若需要其他形式的重试行为(例如处理读/写错误,或针对 `503 Service Unavailable` 做出反应),文档建议考虑 tenacity 这类通用工具 [2]。
检查:引用了 [1, 2, 4],不存在的编号 无,没有引用的句子 0 句(输入 908 词元)
(SSL 検証を無効にする質問の結果も正しかったので、ここでは省略しています。)
リダイレクト:正解で、しかも二つの出典を引用しています。compatibility.md と quickstart.md の両方がこの点に触れているからです。
HTTP/3:ドキュメントにないので、モデルはないと言い、それ以上は一言も言いませんでした。モジュール 01 第 2 課では、同じモデルが資料もルールもない状態で、存在しない CVE 番号をこしらえたのと比べてください。
リトライ:これが最もよくわかる問題です。「httpx には組み込みのリトライがあるか」は、記憶に頼ると「ある」か「ない」かで答えてしまいがちです。資料に沿うと、モデルは正確で節度のある回答をしました。組み込まれているのは接続失敗時のリトライだけで、読み書きのエラーや 503 には別のツールを使う必要がある、と。これはまさに httpx のドキュメントの原文の意味で、モジュール 01 第 2 課で私が同じ点をドキュメントで確認しています。
プログラムで引用をチェックする
モデルは [2] を引用したと言いますが、[2] は本当に存在するのでしょうか。どの文にも引用が付いているのでしょうか。これはプログラムでチェックできます。
def check_citations(text, n_docs):
"""找出回答里所有的 [数字],检查它们是否都在 1..n_docs 范围内,以及有多少句话没有引用。"""
cited = [int(x) for x in re.findall(r"\[(\d+)\]", text)]
invalid = sorted({c for c in cited if not 1 <= c <= n_docs})
prose = re.sub(r"```.*?```", "", text, flags=re.S) # 代码块是照抄文档的,不要求逐行引用
sentences = [s.strip() for s in re.split(r"(?<=[。!?])|\n+", prose) if len(s.strip()) > 8]
# 以冒号结尾的句子是在引出下面的代码,引用通常写在代码块后面,也不算
uncited = [s for s in sentences if not re.search(r"\[\d+\]", s) and not s.endswith((":", ":"))]
return sorted(set(cited)), invalid, uncited
これは二つのことをします。
- 引用した番号が存在するか。資料は 5 つしか渡していないのに、モデルが [7] を引用したら、出典をでっち上げています。4 問では一度も起きませんでしたが、資料が多く回答が長いときには起こりえます。
- 引用のない文はどれか。引用のない文は、モデルが自分で付け加えた内容かもしれません。
このチェッカーは一度直しました。最初の版は、コードブロックの中のコードを 1 行ずつすべて「引用のない文」とみなしたので、コードを含む問題ではどれも誤検出が山のように出ました。コードはドキュメントからそのまま写したもので、1 行ずつ引用する必要はないので、先にコードブロックを取り除いてからチェックするようにしました。今残っている「引用なし」は「ドキュメントに関連する説明は見つかりませんでした」の一文だけで、これはもともと引用が要りません。
プログラムによるチェックには明らかな限界があります。形式はチェックできても、内容はチェックできません。モデルが [2] を引用していても、[2] に本当にその文の根拠があるかどうかは、プログラムには判断できません。それには人が見るか、別のモデルに判断させる必要があり、それが次の課の「忠実度」の評価です。
引用をユーザーに見せる
ユーザーにとって、[1] や [3] のような番号には意味がないので、本当の出典に置き換える必要があります。RepoBot v2 のやり方はこうです。回答が終わった後、回答で実際に引用された番号を探し出し、対応するドキュメントを並べます。
来源:[1] compatibility.md [2] quickstart.md
Web ページにするなら、番号をリンクにして、クリックするとドキュメントの該当箇所に飛べるようにできます。ユーザーは自分で確認でき、誤りがあれば、どの資料がモデルを誤らせたのかがひと目でわかります。
起こりうる問題
モデルが引用しない、あるいは最後に 1 回だけ引用する。期待する引用の形式を示す例をプロンプトに入れましょう。
引用はしているが、引用先が間違っている。回答では A の一節の内容を述べているのに、[B] と付けている。資料どうしの内容が似ているときに起こりやすい誤りです。引用するときに原文の要となる文を抜き出すよう求めれば確認しやすくなりますが、代償として回答が長くなります。
資料どうしが矛盾している。たとえば一方は古いバージョンのドキュメントで、もう一方は新しいバージョンのもの。モデルはどちらかを適当に選ぶかもしれず、両方を混ぜてしまうかもしれません。解決策は検索の側にあります。チャンクにバージョン情報を付け、現行バージョンのドキュメントだけを検索するのです。
慎重すぎる。資料には実は答えがあるのに、書き方が遠回しなので、モデルが「ドキュメントにない」と答えてしまう。これは「資料だけに基づく」ことを求めた副作用です。プロンプトで「資料に間接的に書かれていることも答えてよいが、推論であると明記すること」と説明するとよいでしょう。
練習問題
citations.pyに質問を一つ加えてください。「httpx と requests はどちらのほうが性能がよいですか?」ドキュメントには両者の性能を直接比べた内容はありません。モデルがどう答えるか見てください。- プロンプトから「断片にない情報は、自分で知っていても書かない」のルールを削除して、HTTP/3 の質問をもう一度してください。モデルが自分の知識で答えるか、その内容が正しいかを見てください。
check_citationsにチェックを一つ加えてください。回答がある一節を引用しているなら、その一節には回答の中のコードの識別子(follow_redirectsなど)が少なくとも一つ現れるはずです。これは大まかな「内容のチェック」ですが、どんな場合に誤判定するか考えてみてください。
確認テスト
1. プロンプトに「自分で知っていても、断片にないことは書かない」と書くのはなぜですか?
モデル自身の知識は間違っているかもしれません。たとえば httpx の既定のリダイレクトのふるまいを記憶違いしています。資料だけに基づいて答えるよう求めるのは、モデルの記憶の正しさを資料の正しさで置き換え、しかもどの文も根拠をたどれるようにすることです。代償として、資料にたまたま書かれていない情報は言えなくなります。
2. プログラムのチェックを通過した(引用番号がすべて存在し、どの文にも引用がある)なら、回答は信頼できると言えますか?
言えません。プログラムがチェックできるのは形式だけです。番号が存在するか、引用が付いているか。引用された資料が本当にその文を裏付けているかは判断できません。内容が忠実かどうかは、人が確認するか、別のモデルに判断させる必要があります。
3. モデルに「ドキュメントに関連する説明は見つかりませんでした」というはっきりした言い方を与えるのはなぜですか?
はっきりした逃げ道がないと、資料が足りないときにモデルは自分の知識で補いがちで、それこそが誤りを持ち込む原因になります。決まった言い方を与えれば、モデルは「わからない」も受け入れられる回答だと知り、プログラムもこの一文から、検索で答えが見つからなかったケースを識別できます。
質問と議論
このレッスンでつまずいたところは、ここで質問してください。他の人の質問に答えるのも歓迎です。
質問で 3 ポイント、回答で 6 ポイント。審査を通過すると公開されます。
議論を読み込んでいます…