depiedencap-rag/docs/rag/eval/eval_full.rb
grgmro 39cffbc1bb depiedencap-rag — export public initial
Plugin Discourse RAG (grain post, rerank cross-encoder, citations
whitelistées, gate déterministe, réécriture de requête multi-tour)
+ documentation du chantier (design, éval, sécurité, pipeline).

Export sanitisé depuis le monorepo privé via scripts/export_rag_public.py
(aucun endpoint interne, IP, compte démo ou chemin d'infrastructure).
WIP : agent en test sur groupe restreint, pas encore public sur le forum.
2026-09-17 09:26:45 +02:00

77 lines
2.7 KiB
Ruby

# frozen_string_literal: true
# Éval FULL (génération) — tourne DANS le conteneur (`rails runner`).
# Entrée : JSON [{id, question}] (ARGV[0]). Sortie : JSONL sur stdout.
#
# Pour chaque question, émet les deux versions de l'evidence :
# - prod_block / prod_sources : instructions_block + for_question du
# plugin de prod (retrieval hybrid grain topic, tel qu'envoyé au LLM)
# - posts : top-25 grain post natif (pour construire côté hôte le bloc
# « nouveau pipeline » : rerank → dédup topic → top-6)
#
# La première ligne émise est {"type":"persona","system_prompt":...}.
require "json"
questions = JSON.parse(File.read(ARGV[0]))
persona = AiAgent.find_by(name: "Guide Depiedencap")
puts JSON.generate({ type: "persona", system_prompt: persona&.system_prompt })
$stdout.flush
questions.each do |q|
out = { id: q["id"], question: q["question"] }
%w[category expected_behavior expected_topic_ids
inject_evidence inject_title].each do |k|
out[k] = q[k] if q.key?(k)
end
# --- pipeline prod (grain topic, tel quel) ---------------------------------
begin
out["prod_block"] =
DepiedencapAiCitations::Retrieval.instructions_block(q["question"])
out["prod_sources"] =
DepiedencapAiCitations::Retrieval.for_question(q["question"]).map do |s|
{ title: s[:title], url: s[:url], via: s[:via] }
end
rescue StandardError => e
out["prod_error"] = "#{e.class}: #{e.message}"
end
# --- candidats grain post (nouveau pipeline : rerank côté hôte) ------------
out["posts"] =
begin
qvec = DiscourseAi::Embeddings::Vector.instance.vector_from(q["question"])
sql = <<~SQL
SELECT e.post_id, p.topic_id, p.post_number, t.title, t.slug,
e.embeddings <=> '#{qvec}' AS dist
FROM ai_posts_embeddings e
JOIN posts p ON p.id = e.post_id
JOIN topics t ON t.id = p.topic_id
WHERE e.model_id = 1 AND e.strategy_id = 1
AND p.deleted_at IS NULL
ORDER BY e.embeddings <=> '#{qvec}'
LIMIT 25
SQL
rows = ActiveRecord::Base.connection.execute(sql).to_a
post_ids = rows.map { |r| r["post_id"].to_i }
raws = Post.where(id: post_ids).pluck(:id, :raw).to_h
rows.map do |row|
raw = raws[row["post_id"].to_i].to_s.gsub(/\s+/, " ").strip[0, 300]
{
post_id: row["post_id"].to_i,
topic_id: row["topic_id"].to_i,
post_number: row["post_number"].to_i,
slug: row["slug"],
title: row["title"],
dist: row["dist"].to_f.round(4),
text: "#{row["title"]}. #{raw}",
}
end
rescue StandardError => e
[{ error: "#{e.class}: #{e.message}" }]
end
puts JSON.generate(out)
$stdout.flush
end