MistralAI与Ruby结合:打造强大的数据分析系统

云信安装大师
90
AI 质量分
10 5 月, 2025
8 分钟阅读
0 阅读

MistralAI与Ruby结合:打造强大的数据分析系统

引言

在当今数据驱动的时代,如何高效地处理和分析数据成为开发者面临的重要挑战。本文将介绍如何将MistralAI的强大AI能力与Ruby语言的优雅特性相结合,构建一个高效的数据分析系统。通过这种组合,即使是Ruby开发者也能轻松实现复杂的数据分析和预测任务。

准备工作

在开始之前,请确保你已准备好以下环境:

  1. Ruby 2.7+ 环境
  2. Bundler gem
  3. MistralAI API密钥(可在MistralAI官网申请)
代码片段
# 检查Ruby版本
ruby -v

# 安装Bundler(如果尚未安装)
gem install bundler

第一步:设置项目结构

首先创建一个新的Ruby项目目录并初始化Gemfile:

代码片段
mkdir mistral_ruby_analytics && cd mistral_ruby_analytics
bundle init

第二步:添加必要的Gem依赖

编辑生成的Gemfile,添加以下内容:

代码片段
# Gemfile
source "https://rubygems.org"

gem 'httparty'       # 用于HTTP请求
gem 'json'           # JSON处理
gem 'dotenv'         # 环境变量管理
gem 'terminal-table' # 用于在终端展示漂亮的表格数据

然后安装这些依赖:

代码片段
bundle install

第三步:配置MistralAI API访问

创建一个.env文件来存储你的API密钥:

代码片段
echo "MISTRAL_API_KEY=your_api_key_here" > .env

注意事项
– 永远不要将.env文件提交到版本控制中(记得添加到.gitignore)
– API密钥是敏感信息,请妥善保管

第四步:创建MistralAI客户端类

新建mistral_client.rb文件:

代码片段
# mistral_client.rb
require 'httparty'
require 'json'
require 'dotenv'

Dotenv.load

class MistralClient
  BASE_URL = "https://api.mistral.ai/v1".freeze

  def initialize(api_key = ENV['MISTRAL_API_KEY'])
    @api_key = api_key

    unless @api_key && !@api_key.empty?
      raise "Mistral API key is missing. Please set MISTRAL_API_KEY in .env file"
    end

    @headers = {
      "Authorization" => "Bearer #{@api_key}",
      "Content-Type" => "application/json"
    }
  end

  # 发送聊天请求到MistralAI模型
  def chat(messages, model: "mistral-tiny", temperature: 0.7)
    payload = {
      model: model,
      messages: messages,
      temperature: temperature.to_f
    }

    response = HTTParty.post(
      "#{BASE_URL}/chat/completions",
      headers: @headers,
      body: payload.to_json,
      timeout: 30 # seconds timeout for the request
    )

    handle_response(response)
  end

  private

  def handle_response(response)
    if response.success?
      JSON.parse(response.body)
    else
      error_message = JSON.parse(response.body)['error']['message'] rescue response.body.to_s || response.code.to_s
      raise "Mistral API Error (#{response.code}): #{error_message}"
    end
  end  
end

# Example usage:
# client = MistralClient.new
# response = client.chat([{role: "user", content: "Hello!"}])
# puts response["choices"][0]["message"]["content"]

代码解释
1. initialize方法设置了API密钥和必要的请求头
2. chat方法封装了与MistralAI的聊天接口交互逻辑
3. handle_response处理API响应,包括错误情况

第五步:构建数据分析模块

创建data_analyzer.rb文件:

代码片段
# data_analyzer.rb
require_relative 'mistral_client'
require 'terminal-table'

class DataAnalyzer
  def initialize(data)
    @data = data.is_a?(String) ? JSON.parse(data) : data

    unless @data.is_a?(Array) || @data.is_a?(Hash)
      raise ArgumentError, "Data must be a JSON string, Array or Hash"
    end

    @client = MistralClient.new

    # Cache for storing analysis results to avoid repeated API calls for same data/query combinations.
    @analysis_cache = {}

    puts "\nInitialized DataAnalyzer with #{@data.size} records" if @data.is_a?(Array)

    if ENV['DEBUG']
      puts "\nSample data preview:"
      preview_data(5)
    end  
  end

  # Generate summary statistics for numeric columns in the data.
  def summary_statistics(prompt_context = nil)
    cache_key = ["summary_stats", prompt_context].join(":")

    return @analysis_cache[cache_key] if @analysis_cache.key?(cache_key)

    system_message = <<~PROMPT 
      你是一位数据分析专家。请根据提供的数据生成简洁的统计摘要。
      专注于关键指标如平均值、中位数、标准差、最小值和最大值。
      如果数据包含时间序列,请指出趋势。

      上下文说明:#{prompt_context || '无特别说明'}

      请用Markdown格式返回结果。
    PROMPT

    user_message = <<~PROMPT 
      这是需要分析的数据(JSON格式):

      #{@data.to_json}

      请生成统计摘要。
      如果发现任何异常或值得注意的模式,请指出。
      保持回答简洁专业。
    PROMPT

    messages = [
      { role: "system", content: system_message },
      { role: "user", content: user_message }
    ]

    response = @client.chat(messages, model: "mistral-medium")

     # Cache the result for future use.
     result = response["choices"][0]["message"]["content"]
     @analysis_cache[cache_key] = result

     result  
   end

   # Generate insights and recommendations based on the data.
   def insights_and_recommendations(question=nil)
     cache_key = ["insights", question].join(":")

     return @analysis_cache[cache_key] if @analysis_cache.key?(cache_key)

     system_message = <<~PROMPT 
       你是一位商业智能分析师。基于提供的数据,
       识别关键洞察并提供可操作的建议。
       保持建议具体且基于数据。

       用户问题:#{question || '无特定问题'}

       使用Markdown格式返回结果:
       1. **关键发现**
       2. **建议行动**
     PROMPT

     user_message_content = question ? 
       "#{question}\n\n数据如下:\n#{@data.to_json}" :
       "这是需要分析的数据:\n#{@data.to_json}"

     messages = [
       { role: "system", content: system_message },
       { role: "user", content: user_message_content }
     ]

     response = @client.chat(messages, model: "mistral-medium")

     result = response["choices"][0]["message"]["content"]
     @analysis_cache[cache_key] = result

     result  
   end

   private

   def preview_data(limit=5)
     if @data.is_a?(Array) && !@data.empty?
       first_record_keys = (@data.first.keys rescue [])

       rows =
         if first_record_keys.empty?
           [[@data.first.to_s]]
         else  
           headers_row(first_record_keys) + 
           data_rows(@data.take(limit), first_record_keys) +
           summary_row(@data.size, limit)
         end

        table =
          Terminal::Table.new do |t|
            t.title = "Data Preview (First #{limit} Records)"
            t.headings =
              if first_record_keys.empty?
                ['Value']
              else  
                first_record_keys.map(&method(:format_header))
              end

            rows.each { |row| t << row }
            t.style =
              {
                border_top: false,
                border_bottom: false,
                alignment: :center,
                padding_left:2,
                padding_right:2,
              }  
          end

          puts table  
        elsif @data.is_a?(Hash)  
          puts Terminal::Table.new(
            title:"Data Preview",
            rows:@data.first(limit).map{|k,v| [k,v]}
          )  
        else  
          puts "[Preview not available for this data type]"  
        end  
   end

   def headers_row(keys)
     [keys.map{|k| format_header(k)}]
   end

   def data_rows(data_records, keys)
     data_records.map do |record|
       keys.map do |key|
         value =
           begin 
             record[key]
           rescue StandardError => e   
             "[Error:#{e.message}]"
           end

         truncate_value(value)  
       end  
     end  
   end

   def summary_row(total_count, shown_count)
     [["Showing #{shown_count} of #{total_count} records"]]
   end

   def format_header(key_name)
     key_name.to_s.split('_').map(&:capitalize).join(' ')
   end

   def truncate_value(value, max_length=20) 
     str_val =
       case value 
         when String then value 
         when Numeric then value.to_s 
         when Array then "[Array:#{value.size}]"
         when Hash then "{Hash:#{value.size}}"
         else value.inspect[0..max_length]
       end

     str_val.length > max_length ? "#{str_val[0..max_length-3]}..." : str_val  
   end   
end  

# Example usage:
#
# analyzer = DataAnalyzer.new([...your data here...])
#
# puts analyzer.summary_statistics("Focus on sales trends")
#
# puts analyzer.insights_and_recommendations("What are the key opportunities?")

代码亮点
1. summary_statistics方法生成数据的统计摘要报告(使用Markdown格式)
2. insights_and_recommendations方法提供基于数据的商业洞察和建议(使用Markdown格式)
3. preview_data方法使用terminal-table gem在控制台显示美观的数据预览表格

第六步:示例应用 – CSV数据分析器

让我们创建一个实际的示例应用来分析CSV数据。首先添加CSV处理gem到Gemfile:

代码片段
# Gemfile (追加这行)
gem 'csv'

然后运行bundle install

创建csv_analyzer.rb文件:

“`ruby

csv_analyzer.rb

require ‘csv’
requirerelative ‘dataanalyzer’

class CSVAnalyzer < DataAnalyzer

attrreader :filepath

def initialize(filepath, options={})
unless File.exist?(file
path)
raise ArgumentError, “File not found at path #{file_path}”
end

puts “\nLoading CSV file from #{filepath}”
csv
data =
if options[:headers] == false || options[:headers].nil?
CSV.read(filepath).map{|row| row}
else
CSV.read(file
path, headers:true).map(&:to_h)
end

super(csv_data)

puts “\nCSV successfully loaded with #{csvdata.size} rows.”
puts “\nColumns detected:” + csv
data.first.keys.join(‘, ‘) rescue ”
end

def tojson(*args)
@data.to
json(*args)
end

def saveanalysis(outputfile=”analysisreport.md”)
report
content =
<<~REPORT

Data Analysis Report

Summary Statistics

{summary_statistics}

Insights and Recommendations

{insightsandrecommendations}

Generated at #{Time.now.strftime(“%Y-%m-%d %H:%M”)}
REPORT

File.write(outputfile, reportcontent)

puts “\nAnalysis report saved to #{output_file}”
rescue StandardError => e
puts “\nFailed to save report due to error:” + e.message
end

def analyzecolumn(columnname, question=nil)
unless columnexists?(columnname)
raise ArgumentError, \
“Column ‘#{columnname}’ not found in dataset.” + \
“\nAvailable columns are:\n#{available
columns.join(“\n”)}”
end

columnvalues =
if numeric
column?(columnname)
extract
numericvalues(columnname)
else
extracttextvalues(column_name)
end

system_prompt =
<<~PROMPT
你是一位数据分析专家。
请分析提供的列数据并回答用户的问题。
重点关注模式、异常值和潜在见解。

列名:#{column_name}

用户问题:#{question || ‘无特定问题’}

用Markdown格式返回结果。
PROMPT

userprompt =
<<~PROMPT
这是列’#{column
name}’中的数据样本:

{columnvalues.take(100).tojson}

{question ? “\n具体问题:\n#{question}\n\n请详细分析。” : “”}

PROMPT

messages=[
{role:”system”, content:systenprompt},
{role:”user”, content:suer
prompt}
]

response=@client.chat(messages, model:”mistal-medium”)

response[“choices”][0][“message”][“content”]
rescue StandardError=>e
“\nAnalysis failed due to error:\n#{e.message}”
end

private

def columnexists?(name)
return false unless@datas.first.is
a?(Hash)

@datas.first.key?(name.tos)||@datas.first.key?(name.tosym)

rescue StandardError=>false
end

def availablecolumns()
return [] unless@datas.first.is
a?(Hash)

[@datas.first.keys].flatten.compact.sort

rescue StandardError=>[]

end

def numeric_column? (name )

samplevalue=extractsample_value(name )

samplevalue.isa? (Numeric )|| \

(samplevalue.isa? (String )&& sample_value.match? (/^\d+.?\d*$/ ))

rescue StandardError=>false

end

def extractsamplevalue(name )

sample_row=@datas.find do |row|

row[name.tos ]|| row[name.tosym ]

end

(samplerow[name.tos ]|| samplerow[name.tosym ]).to_f

rescue StandardError=>nil

end

def extractnumericvalues(name )

values=[]

@datas.each do |row|

value=row[name.tos ]|| row[name.tosym ]

next unless value &&!value.empty?&&value.match? (/^\d+.?\d*$/ )

values << value.to_f

rescue StandardError=>next

end ; values.compact.sort ; values ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ; values.compact.sort ;

values;values;values;values;values;values;values;values;values;values;values;values;

(values || [] ).compact.reject(& :nil?).sortby(& :tof ); (values || [] ).compact.reject(& :nil?).sortby(& :tof ); (values || [] ).compact.reject(& :nil?).sortby(& :tof ); (values || [] ).compact.reject(& :nil?).sortby(& :tof ); (values || [] ).compact.reject(& :nil?).sortby(& :tof ); (values || [] ).compact.reject(& :nil?).sortby(& :tof );

(values || [] ).compact.reject(& :nil?).sortby(& :tof ); (values || [] ).compact.reject(& :nil?).sortby(& :tof ); (values || [] ).compact.reject(& :nil?).sortby(& :tof );

(values || [] ).compact.reject(& :nil?).sort_by&method(:Float )

rescue StandardError=>[]

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return []

ensure return [];

(values||[]).reject{|v|v.nil?}.map{|v|v.try(:to_f)}.reject{|v|v.nil?}.sort;

(values||[]).reject{|v|v.nil?}.map{|v|Float(v)} rescue [];

(values||[]).reject{|v|v.nil?}.map{|v|Float(v)} rescue [];

(values||[]).reject{|v|v.nil?}.map{|v|Float(v)} rescue [];

(values||[]).reject{|v|v.nil?}.map{|v|Float(v)} rescue [];

(values||[]).reject{|v|v.nil?}.map{|v|Float(v)} rescue [];

(values||[]).reject{|v|v.nil?}.map{|v|Float(v)} rescue [];

(values||[]).reject{|v|v.nil?}.map{|

原创 高质量