Des réponses avec citations
Transmettre au modèle les morceaux trouvés, numérotés, en exigeant qu'il indique la source de chaque phrase et qu'il dise quand l'information manque. Puis vérifier par programme que les citations sont valides, et voir s'il invente face à une question dont la documentation ne contient pas la réponse.
- Environ 35 minutes
- Niveau : Intermédiaire
- Testé : 2026-09-14 deepseek-flash, multilingual-e5-small
Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.
La recherche est au point ; il faut maintenant transmettre au modèle les documents trouvés. La manière de les transmettre, et ce qu'on exige ensuite, décide de la fiabilité de la réponse finale.
Cette leçon vise trois objectifs : le modèle ne répond que d'après les documents, indique pour chaque phrase le passage sur lequel elle s'appuie, et dit honnêtement quand les documents ne contiennent pas la réponse. Puis on écrit un programme qui vérifie automatiquement s'il l'a fait.
Numéroter les documents
La recherche utilise la meilleure méthode de la leçon précédente, « réécriture + hybride RRF », et garde les 5 premiers morceaux. Chaque morceau est enveloppé dans une balise portant un numéro et une source :
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>"
Dans le prompt, cela ressemble à ceci (contenu omis) :
<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 默认会自动跟随重定向吗?怎么开启?
Le numéro sert au modèle pour citer ; source sert de repère au modèle et permet au programme de l'afficher à l'utilisateur à la fin. La leçon 1 du module 02 a montré comment séparer matériau et instructions par des balises ; la balise <doc> a ici un rôle de plus : marquer clairement les limites de chaque passage, pour que le modèle ne mélange pas deux passages en un.
Le prompt
SYSTEM = """你是 httpx 的答疑助手。只根据 <docs> 里的文档片段回答用户的问题。
规则:
- 每句话后面用 [编号] 注明依据的是哪个片段,可以同时引用多个,如 [1][3]。
- 片段里没有的信息不要写,哪怕你自己知道。
- 如果这些片段不足以回答问题,就直接说"文档里没有找到相关说明",不要猜。
- 用中文回答,简洁,代码保持原样。"""
Dans la deuxième règle, « même si tu le sais toi-même » est important. Le modèle en sait déjà beaucoup sur httpx, mais ce qu'il sait peut être faux, par exemple que les redirections sont suivies par défaut. Exiger qu'il n'utilise que les documents, c'est remplacer l'exactitude de la mémoire du modèle par celle des documents. Le prix : ce que les documents ne disent pas mais que le modèle sait en réalité, il ne peut pas le dire non plus. La leçon 7 rencontrera un tel exemple.
La troisième règle donne au modèle une « porte de sortie » claire. Sans elle, quand les documents ne suffisent pas, le modèle comble souvent le manque avec ses propres connaissances, et c'est justement ce qu'on veut éviter.
Résultats
J'ai préparé 4 questions : deux dont la documentation contient une réponse claire ; une que la documentation ne mentionne pas du tout (HTTP/3 : j'ai cherché dans toute la documentation de httpx, il n'y apparaît jamais) ; et une dont la réponse demande de lire attentivement les documents (httpx a-t-il des nouvelles tentatives intégrées ?). Code complet dans code/04-rag/citations.py, résultat :
问: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 词元)
(La question sur la désactivation de la vérification SSL a aussi reçu une bonne réponse, omise ici.)
Redirections : juste, avec deux sources, car compatibility.md et quickstart.md en parlent tous deux.
HTTP/3 : la documentation n'en parle pas, le modèle le dit, sans un mot de plus. En comparaison, à la leçon 2 du module 01, le même modèle, sans documents ni règles, avait inventé un numéro de CVE inexistant.
Nouvelles tentatives : la question la plus parlante. « httpx a-t-il des nouvelles tentatives intégrées ? » : de mémoire, on répond facilement « oui » ou « non » en bloc. D'après les documents, le modèle a donné une réponse précise et nuancée : seules les nouvelles tentatives en cas d'échec de connexion sont intégrées ; pour les erreurs de lecture ou d'écriture et les 503, il faut d'autres outils. C'est exactement ce que dit la documentation de httpx ; j'avais vérifié ce même point dans la documentation à la leçon 2 du module 01.
Vérifier les citations par programme
Le modèle dit citer [2], mais [2] existe-t-il vraiment ? Chaque phrase a-t-elle une citation ? Cela se vérifie par programme :
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
Il fait deux choses :
- Les numéros cités existent-ils ? Si on ne donne que 5 passages et que le modèle cite [7], il invente une source. Ce n'est arrivé sur aucune des 4 questions, mais cela se produit avec beaucoup de documents et de longues réponses.
- Quelles phrases n'ont pas de citation ? Une phrase sans citation est peut-être un contenu ajouté par le modèle lui-même.
J'ai corrigé ce vérificateur une fois. La première version considérait chaque ligne de code d'un bloc de code comme une « phrase sans citation », et chaque question avec du code donnait une pluie de fausses alertes. Le code est recopié de la documentation et n'a pas besoin de citation ligne par ligne ; on retire donc les blocs de code avant de vérifier. Désormais, la seule phrase « non citée » est « 在文档里没有找到相关说明 » (rien de pertinent n'a été trouvé dans la documentation), qui n'a de toute façon pas besoin de citation.
La vérification par programme a une limite évidente : elle vérifie la forme, pas le fond. Si le modèle cite [2], le programme ne peut pas juger si [2] étaye vraiment la phrase. Il faut pour cela un humain, ou un autre modèle comme juge : c'est l'évaluation de la « fidélité » de la leçon suivante.
Montrer les citations à l'utilisateur
Pour l'utilisateur, des numéros comme [1] ou [3] ne veulent rien dire ; il faut les remplacer par les vraies sources. RepoBot v2 procède ainsi : une fois la réponse terminée, il repère quels numéros ont réellement été cités et liste les documents correspondants :
来源:[1] compatibility.md [2] quickstart.md
Sur une page web, on peut faire des numéros des liens qui mènent directement à l'endroit concerné de la documentation. L'utilisateur peut vérifier lui-même, et en cas d'erreur, on voit tout de suite quel passage a induit le modèle en erreur.
Problèmes possibles
Le modèle ne cite pas, ou ne cite qu'une fois à la fin. Donnez un exemple dans le prompt, montrant le format de citation attendu.
Il cite, mais de travers. La réponse reprend le contenu du passage A mais indique [B]. C'est fréquent quand des passages se ressemblent. On peut exiger qu'il recopie la phrase clé d'origine en citant, ce qui facilite la vérification mais allonge la réponse.
Les documents se contredisent. Par exemple un passage d'une ancienne version de la documentation et un d'une nouvelle. Le modèle peut en choisir un au hasard, ou mélanger les deux. La solution est du côté de la recherche : ajouter l'information de version aux morceaux et ne chercher que dans la version courante.
Trop prudent. Les documents contiennent la réponse, mais de façon implicite, et le modèle répond « la documentation n'en parle pas ». C'est l'effet secondaire de « uniquement d'après les documents ». On peut préciser dans le prompt : « ce que les documents mentionnent indirectement peut être répondu, en signalant qu'il s'agit d'une déduction ».
Exercices
- Ajoutez à
citations.pyla question « Lequel est le plus performant, httpx ou requests ? ». La documentation ne compare pas directement leurs performances ; voyez comment le modèle répond. - Supprimez du prompt la règle « n'écris pas d'information absente des passages, même si tu la connais », reposez la question sur HTTP/3, et voyez si le modèle répond avec ses propres connaissances, et si ce qu'il dit est juste.
- Ajoutez une vérification à
check_citations: si la réponse cite un passage, ce passage doit contenir au moins un des identifiants de code de la réponse (par exemplefollow_redirects). C'est une « vérification du fond » grossière ; réfléchissez aux cas où elle se trompe.
Auto-test
1. Pourquoi le prompt dit-il « n'écris pas ce qui n'est pas dans les passages, même si tu le sais » ?
Les connaissances du modèle peuvent être fausses, par exemple sur le comportement par défaut de httpx face aux redirections. Répondre uniquement d'après les documents remplace l'exactitude de la mémoire du modèle par celle des documents, et rend chaque phrase vérifiable. Le prix : ce qui manque par hasard dans les documents ne peut pas être dit.
2. La vérification par programme passe (tous les numéros cités existent, chaque phrase a une citation). La réponse est-elle fiable pour autant ?
Non. Le programme ne vérifie que la forme : existence des numéros, présence des citations. Il ne peut pas juger si le passage cité étaye vraiment la phrase. La fidélité du fond doit être vérifiée par un humain, ou jugée par un autre modèle.
3. Pourquoi donner au modèle une formule explicite comme « rien de pertinent n'a été trouvé dans la documentation » ?
Sans porte de sortie claire, quand les documents ne suffisent pas, le modèle a tendance à compléter avec ses propres connaissances, ce qui peut justement introduire des erreurs. Avec une formule fixe, il sait que « je ne sais pas » est une réponse acceptable, et le programme peut reconnaître à cette phrase les cas où la recherche n'a pas trouvé la réponse.
Questions et discussion
Bloqué sur cette leçon ? Posez votre question ici. Et si vous pouvez répondre à quelqu'un, n'hésitez pas.
Une question rapporte 3 points, une réponse 6. Les messages paraissent après vérification.
Chargement de la discussion…