class CharacterProfileGenerator
Constants
- MAX_SOURCE_CHARS
- USER_AGENT
Public Class Methods
Source
# File lib/character_profile_generator.rb, line 61 def api_url_for(url) uri = URI.parse(url) uri.path = "/api.php" uri.query = URI.encode_www_form( action: "query", prop: "extracts|info", explaintext: "1", inprop: "url", redirects: "1", titles: title_from_url(url), format: "json", utf8: "1" ) uri.to_s end
Source
# File lib/character_profile_generator.rb, line 83 def build_prompt(source_url:, title:, source_text:) source_text = clean_text(source_text)[0, MAX_SOURCE_CHARS] <<~PROMPT Create a compact IRC bot character profile from this Aqua Teen Hunger Force wiki source. Output strict YAML only. Do not wrap it in markdown. Use exactly these top-level keys: name: string source_url: string summary: string traits: array of short strings voice: array of short strings canon: array of short strings rules: array of short strings examples: array of mappings with user and bot strings Requirements: - Preserve canon from the source. - Do not invent detailed backstory beyond the source. - Make the voice usable in IRC: short, punchy, and character-specific. - Include 8 to 12 examples. - The bot should stay in character while still answering normal user prompts when possible. - Avoid shell, filesystem, credential, or environment-variable references. Character title: #{title} Source URL: #{source_url} Source text: #{source_text} PROMPT end
Source
# File lib/character_profile_generator.rb, line 121 def clean_text(text) text.to_s .gsub(/\[[^\]]+\]/, " ") .gsub(/[ \t]+/, " ") .gsub(/\r?\n[ \t]*/, "\n") .squeeze("\n") .strip end
Source
# File lib/character_profile_generator.rb, line 130 def default_fetch(url) RabbotHttp.fetch(url, user_agent: USER_AGENT) end
Source
# File lib/character_profile_generator.rb, line 47 def fetch_api_source(url, fetch: method(:default_fetch)) body = fetch.call(api_url_for(url)) data = JSON.parse(body) page = data.dig("query", "pages")&.values&.find { |candidate| candidate["missing"].nil? } return { title: title_from_url(url), text: "" } unless page { title: page["title"].to_s.strip, text: clean_text(page["extract"]) } rescue JSON::ParserError, StandardError { title: title_from_url(url), text: "" } end
Source
# File lib/character_profile_generator.rb, line 36 def fetch_source(url, fetch: method(:default_fetch)) api_source = fetch_api_source(url, fetch: fetch) return api_source if api_source[:text] && !api_source[:text].empty? html = fetch.call(url) { title: title_from_url(url), text: clean_text(Nokogiri::HTML(html).text) } end
Source
# File lib/character_profile_generator.rb, line 19 def generate(url, runner:, model: nil, fetch: method(:default_fetch)) source = fetch_source(url, fetch: fetch) prompt = build_prompt(source_url: url, title: source[:title], source_text: source[:text]) result = model ? runner.call(prompt, model: model) : runner.call(prompt) raise Error, result.text.to_s.strip unless result.respond_to?(:success?) && result.success? parse_profile(result.text) end
Source
# File lib/character_profile_generator.rb, line 114 def parse_profile(text) yaml = strip_markdown_fence(text.to_s) CharacterProfile.from_hash(YAML.safe_load(yaml, aliases: false)) rescue Psych::SyntaxError, CharacterProfile::Error => e raise Error, "Generated profile is invalid: #{e.message}" end
Source
# File lib/character_profile_generator.rb, line 77 def title_from_url(url) path = URI.parse(url).path.to_s title = path.split("/").last.to_s CGI.unescape(title).tr("_", " ").strip end
Source
# File lib/character_profile_generator.rb, line 28 def write(url, output_path, runner:, model: nil, fetch: method(:default_fetch)) profile = generate(url, runner: runner, model: model, fetch: fetch) expanded_path = File.expand_path(output_path) FileUtils.mkdir_p(File.dirname(expanded_path)) File.write(expanded_path, "#{YAML.dump(profile.data)}\n") profile end