Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 84fa41b

Browse files
committed
infra for pdf evals
1 parent 08e016c commit 84fa41b

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
lines changed

app/models/llm_model.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def self.provider_params
7070
end
7171

7272
def to_llm
73-
DiscourseAi::Completions::Llm.proxy(identifier)
73+
DiscourseAi::Completions::Llm.proxy(self)
7474
end
7575

7676
def identifier

evals/lib/llm.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ class DiscourseAi::Evals::Llm
5555
max_prompt_tokens: 1_000_000,
5656
vision_enabled: true,
5757
},
58+
"gemini-2.0-pro-exp" => {
59+
display_name: "Gemini 2.0 pro",
60+
name: "gemini-2-0-pro-exp",
61+
tokenizer: "DiscourseAi::Tokenizer::GeminiTokenizer",
62+
api_key_env: "GEMINI_API_KEY",
63+
provider: "google",
64+
url: "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-pro-exp",
65+
max_prompt_tokens: 1_000_000,
66+
vision_enabled: true,
67+
},
5868
}
5969

6070
def self.choose(config_name)
@@ -97,6 +107,8 @@ def eval(type:, args:, expected_output: nil, expected_output_regex: nil)
97107
case type
98108
when "helper"
99109
helper(**args)
110+
when "pdf_to_text"
111+
pdf_to_text(**args)
100112
end
101113

102114
if expected_output
@@ -121,8 +133,38 @@ def name
121133
@llm_model.display_name
122134
end
123135

136+
def vision?
137+
@llm_model.vision_enabled
138+
end
139+
124140
private
125141

142+
def pdf_to_text(path:)
143+
upload =
144+
UploadCreator.new(File.open(path), File.basename(path)).create_for(Discourse.system_user.id)
145+
146+
uploads =
147+
DiscourseAi::Utils::PdfToImages.new(
148+
upload: upload,
149+
user: Discourse.system_user,
150+
).uploaded_pages
151+
152+
text = +""
153+
uploads.each do |page_upload|
154+
DiscourseAi::Utils::ImageToText
155+
.new(upload: page_upload, llm_model: @llm_model, user: Discourse.system_user)
156+
.extract_text do |chunk, error|
157+
text << chunk if chunk
158+
text << "\n\n" if chunk
159+
end
160+
upload.destroy
161+
end
162+
163+
text
164+
ensure
165+
upload.destroy if upload
166+
end
167+
126168
def helper(input:, name:)
127169
completion_prompt = CompletionPrompt.find_by(name: name)
128170
helper = DiscourseAi::AiHelper::Assistant.new(helper_llm: @llm)

evals/run

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
#!/usr/bin/env ruby
22
# frozen_string_literal: true
33

4+
# got to ensure evals are here
5+
# rubocop:disable Discourse/Plugins/NamespaceConstants
6+
EVAL_PATH = File.join(__dir__, "cases")
7+
# rubocop:enable Discourse/Plugins/NamespaceConstants
8+
#
9+
if !Dir.exist?(EVAL_PATH)
10+
puts "Evals are missing, cloning from discourse/discourse-ai-evals"
11+
12+
success =
13+
system("git clone [email protected]:discourse/discourse-ai-evals.git '#{EVAL_PATH}' 2>/dev/null")
14+
15+
# Fall back to HTTPS if SSH fails
16+
if !success
17+
puts "SSH clone failed, falling back to HTTPS..."
18+
success = system("git clone https://github.com/discourse/discourse-ai-evals.git '#{EVAL_PATH}'")
19+
end
20+
21+
if success
22+
puts "Successfully cloned evals repository"
23+
else
24+
abort "Failed to clone evals repository"
25+
end
26+
end
27+
428
discourse_path = File.expand_path(File.join(File.dirname(__FILE__), "../../.."))
529
# rubocop:disable Discourse/NoChdir
630
Dir.chdir(discourse_path)
@@ -27,10 +51,8 @@ OptionParser
2751
end
2852
.parse!
2953

30-
# Ensure output directory exists
3154
FileUtils.mkdir_p(options[:output_dir])
3255

33-
# Load and run the specified evaluation
3456
if options[:eval_name].nil?
3557
puts "Error: Must specify an evaluation name with -e or --eval"
3658
exit 1
@@ -54,11 +76,21 @@ end
5476

5577
eval_info = YAML.load_file(cases[options[:eval_name]]).symbolize_keys
5678

79+
# correct relative paths in args
80+
begin
81+
eval_info[:args]&.each do |k, v|
82+
if k.to_sym == :path
83+
root = File.dirname(cases[options[:eval_name]])
84+
eval_info[:args][k] = File.join(root, v)
85+
end
86+
end
87+
end
88+
5789
puts "Running evaluation '#{options[:eval_name]}'"
5890

5991
log_filename = "#{options[:eval_name]}-#{Time.now.strftime("%Y%m%d-%H%M%S")}.log"
6092
logs_dir = File.join(__dir__, "log")
61-
FileUtils.mkdir_p(logs_dir) # Create directory if it doesn't exist
93+
FileUtils.mkdir_p(logs_dir)
6294
log_file = File.join(logs_dir, log_filename)
6395

6496
logger = Logger.new(File.open(log_file, "a"))
@@ -68,6 +100,11 @@ logger.info("Starting evaluation '#{options[:eval_name]}'")
68100
Thread.current[:llm_audit_log] = logger
69101

70102
llms.each do |llm|
103+
if eval_info[:vision] && !llm.vision?
104+
logger.info("Skipping LLM: #{llm.name} as it does not support vision")
105+
next
106+
end
107+
71108
logger.info("Evaluating with LLM: #{llm.name}")
72109
eval =
73110
llm.eval(

0 commit comments

Comments
 (0)