Ruby OpenAI
使用 OpenAI API 与 Ruby 一起!🤖❤️
使用 GPT-4o 流式传输文本,使用 Whisper 转录和翻译音频,或使用 DALL·E 创建图像...
📚 Rails AI(免费书籍) | 🎮 Ruby AI Builders Discord | 🐦 X | 🧠 Anthropic Gem | 🚂 Midjourney Gem
目录
安装
Bundler
将这一行添加到应用程序的 Gemfile 中:
gem "ruby-openai"
然后执行:
$ bundle install
Gem 安装
或者通过以下方式安装:
$ gem install ruby-openai
并通过以下方式引入:
require "openai"
使用
- 从 https://platform.openai.com/account/api-keys 获取您的 API 密钥
- 如果您属于多个组织,可以从 https://platform.openai.com/account/org-settings 获取您的组织 ID
快速入门
对于快速测试,您可以将令牌直接传递给新客户端:
client = OpenAI::Client.new(
access_token: "access_token_goes_here",
log_errors: true # 强烈推荐在开发中使用,以便查看 OpenAI 返回的错误。不推荐在生产环境中使用,因为它可能会泄露私密数据到日志中。
)
使用配置
对于更强大的设置,您可以通过 API 密钥配置 gem,例如在 openai.rb
初始化文件中。永远不要将密钥硬编码到代码库中 - 使用类似 dotenv 的工具将密钥安全地传递到您的环境中。
OpenAI.configure do |config|
config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID") # 可选
config.log_errors = true # 强烈推荐在开发中使用,以便查看 OpenAI 返回的错误。不推荐在生产环境中使用,因为它可能会泄露私密数据到日志中。
end
然后您可以像这样创建一个客户端:
client = OpenAI::Client.new
您仍然可以在创建新客户端时覆盖配置的默认值;未包含的任何选项将回退到使用 OpenAI.configure 设置的任何全局配置。例如在这个例子中,organization_id、request_timeout 等会回退到任何使用 OpenAI.configure 全局设置的配置,仅 access_token 被覆盖:
client = OpenAI::Client.new(access_token: "access_token_goes_here")
自定义超时或基本 URI
使用此库的任何请求的默认超时为 120 秒。您可以通过在初始化客户端时传递秒数来更改此设置。您还可以更改用于所有请求的基本 URI,例如使用类似 Helicone 的可观测工具,并添加任意其他头信息,例如用于 openai-caching-proxy-worker:
client = OpenAI::Client.new(
access_token: "access_token_goes_here",
uri_base: "https://oai.hconeai.com/",
request_timeout: 240,
extra_headers: {
"X-Proxy-TTL" => "43200", # 用于 https://github.com/6/openai-caching-proxy-worker#specifying-a-cache-ttl
"X-Proxy-Refresh": "true", # 用于 https://github.com/6/openai-caching-proxy-worker#refreshing-the-cache
"Helicone-Auth": "Bearer HELICONE_API_KEY", # 用于 https://docs.helicone.ai/getting-started/integration-method/openai-proxy
"helicone-stream-force-format" => "true", # 使用此选项与 Helicone 否则流传输会丢失块 # https://github.com/alexrudall/ruby-openai/issues/251
}
)
或在配置 gem 时:
OpenAI.configure do |config|
config.access_token = ENV.fetch("OPENAI_ACCESS_TOKEN")
config.log_errors = true # 可选
config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID") # 可选
config.uri_base = "https://oai.hconeai.com/" # 可选
config.request_timeout = 240 # 可选
config.extra_headers = {
"X-Proxy-TTL" => "43200", # 用于 https://github.com/6/openai-caching-proxy-worker#specifying-a-cache-ttl
"X-Proxy-Refresh": "true", # 用于 https://github.com/6/openai-caching-proxy-worker#refreshing-the-cache
"Helicone-Auth": "Bearer HELICONE_API_KEY" # 用于 https://docs.helicone.ai/getting-started/integration-method/openai-proxy
} # 可选
end
每个客户端的额外头信息
您可以动态地为每个客户端对象传递头信息,这些头信息
您可以实时从API流式传输,这样可以更快并用于创建更具吸引力的用户体验。将Proc(或任何具有#call
方法的对象)传递给stream
参数,以接收生成的完成块流。每次接收到一个或多个块时,proc会针对每个块调用一次,并将其解析为Hash。如果OpenAI返回错误,ruby-openai
将引发Faraday错误。
client.chat(
parameters: {
model: "gpt-4o", # 必填
messages: [{ role: "user", content: "描述一个叫安娜的角色!"}], # 必填
temperature: 0.7,
stream: proc do |chunk, _bytesize|
print chunk.dig("choices", 0, "delta", "content")
end
})
# => "安娜是一个二十多岁的年轻女子,棕色波浪发垂到肩膀..."
注意:为了获取使用信息,您可以提供stream_options
参数,OpenAI将提供一个包含使用信息的最终块。以下是一个示例:
stream_proc = proc { |chunk, _bytesize| puts "--------------"; puts chunk.inspect; }
client.chat(
parameters: {
model: "gpt-4o",
stream: stream_proc,
stream_options: { include_usage: true },
messages: [{ role: "user", content: "你好!"}],
})
# => --------------
# => {"id"=>"chatcmpl-7bbq05PiZqlHxjV1j7OHnKKDURKaf", "object"=>"chat.completion.chunk", "created"=>1718750612, "model"=>"gpt-4o-2024-05-13", "system_fingerprint"=>"fp_9cb5d38cf7", "choices"=>[{"index"=>0, "delta"=>{"role"=>"assistant", "content"=>""}, "logprobs"=>nil, "finish_reason"=>nil}], "usage"=>nil}
# => --------------
# => {"id"=>"chatcmpl-7bbq05PiZqlHxjV1j7OHnKKDURKaf", "object"=>"chat.completion.chunk", "created"=>1718750612, "model"=>"gpt-4o-2024-05-13", "system_fingerprint"=>"fp_9cb5d38cf7", "choices"=>[{"index"=>0, "delta"=>{"content"=>"你好"}, "logprobs"=>nil, "finish_reason"=>nil}], "usage"=>nil}
# => --------------
# => ...更多内容块
# => --------------
# => {"id"=>"chatcmpl-7bbq05PiZqlHxjV1j7OHnKKDURKaf", "object"=>"chat.completion.chunk", "created"=>1718750612, "model"=>"gpt-4o-2024-05-13", "system_fingerprint"=>"fp_9cb5d38cf7", "choices"=>[{"index"=>0, "delta"=>{}, "logprobs"=>nil, "finish_reason"=>"stop"}], "usage"=>nil}
# => --------------
# => {"id"=>"chatcmpl-7bbq05PiZqlHxjV1j7OHnKKDURKaf", "object"=>"chat.completion.chunk", "created"=>1718750612, "model"=>"gpt-4o-2024-05-13", "system_fingerprint"=>"fp_9cb5d38cf7", "choices"=>[], "usage"=>{"prompt_tokens"=>9, "completion_tokens"=>9, "total_tokens"=>18}}
Vision
您可以使用GPT-4 Vision模型生成图像描述:
messages = [
{ "type": "text", "text": "这张图片里有什么?"},
{ "type": "image_url",
"image_url": {
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg",
},
}
]
response = client.chat(
parameters: {
model: "gpt-4-vision-preview", # 必填
messages: [{ role: "user", content: messages}], # 必填
})
puts response.dig("choices", 0, "message", "content")
# => "这张图片展示了一个宁静的自然景观,长长的木板路一直延伸向前。"
JSON模式
您可以设置response_format
参数以请求JSON格式的响应:
response = client.chat(
parameters: {
model: "gpt-4o",
response_format: { type: "json_object" },
messages: [{ role: "user", content: "你好!请给我一些JSON。" }],
temperature: 0.7,
})
puts response.dig("choices", 0, "message", "content")
{
"name": "John",
"age": 30,
"city": "New York",
"hobbies": ["reading", "traveling", "hiking"],
"isStudent": false
}
您也可以流式传输!
response = client.chat(
parameters: {
model: "gpt-4o",
messages: [{ role: "user", content: "可以给我一些JSON吗?"}],
response_format: { type: "json_object" },
stream: proc do |chunk, _bytesize|
print chunk.dig("choices", 0, "delta", "content")
end
})
{
"message": "当然,请告诉我你想要什么特定的JSON数据。",
"JSON_data": {
"example_1": {
"key_1": "value_1",
"key_2": "value_2",
"key_3": "value_3"
},
"example_2": {
"key_4": "value_4",
"key_5": "value_5",
"key_6": "value_6"
}
}
}
Functions
您可以描述并传递函数,模型会智能地选择输出一个包含调用参数的JSON对象。例如,使用您的方法get_current_weather
获取给定位置的天气信息。请注意,tool_choice
是可选的,但如果不包含它,模型将决定是否使用该函数(请参见此处)。
def get_current_weather(location:, unit: "fahrenheit")
# 您可以在此处使用天气API来获取天气。
"在#{location}的天气很好 🌞 #{unit}"
end
messages = [
{
"role": "user",
"content": "旧金山的天气如何?",
},
]
response =
client.chat(
parameters: {
model: "gpt-4o",
messages: messages, # 上面定义,因为我们将再次使用它
tools: [
{
type: "function",
function: {
name: "get_current_weather",
description: "获取给定位置的当前天气",
parameters: { # 格式: https://json-schema.org/understanding-json-schema
type: :object,
properties: {
location: {
type: :string,
description: "城市和州,例如旧金山,CA",
},
unit: {
type: "string",
enum: %w[celsius fahrenheit],
},
},
required: ["location"],
},
},
}
],
tool_choice: "required" # 可选,默认为“auto”
# 也可以使用“none”或特定函数,详见文档
},
)
message = response.dig("choices", 0, "message")
if message["role"] == "assistant" && message["tool_calls"]
message["tool_calls"].each do |tool_call|
tool_call_id = tool_call.dig("id")
function_name = tool_call.dig("function", "name")
function_args = JSON.parse(
tool_call.dig("function", "arguments"),
{ symbolize_names: true },
)
function_response = case function_name
when "get_current_weather"
get_current_weather(**function_args) # => "天气很好 🌞"
else
# 决定如何处理
end
# 对于角色为“tool”的后续消息,OpenAI要求前面的消息具有tool_calls参数。
messages << message
messages << {
tool_call_id: tool_call_id,
role: "tool",
name: function_name,
content: function_response
} # 使用函数结果扩展对话
end
second_response = client.chat(
parameters: {
model: "gpt-4o",
messages: messages
})
puts second_response.dig("choices", 0, "message", "content")
# 此时,模型已经决定调用函数,您
这将给你微调模型的ID。如果你犯了错误,可以在微调模型被处理之前取消它:
```ruby
client.finetunes.cancel(id: fine_tune_id)
你可能需要等待一段时间来完成处理。一旦处理完成,你可以使用list或retrieve来获取微调模型的名称:
client.finetunes.list
response = client.finetunes.retrieve(id: fine_tune_id)
fine_tuned_model = response["fine_tuned_model"]
然后可以在聊天补全中使用这个微调模型名称:
response = client.chat(
parameters: {
model: fine_tuned_model,
messages: [{ role: "user", content: "I love Mondays!"}]
}
)
response.dig("choices", 0, "message", "content")
你也可以捕捉一个任务的事件:
client.finetunes.list_events(id: fine_tune_id)
向量存储
向量存储对象使文件搜索工具能够搜索你的文件。
你可以创建一个新的向量存储:
response = client.vector_stores.create(
parameters: {
name: "my vector store",
file_ids: ["file-abc123", "file-def456"]
}
)
vector_store_id = response["id"]
给定一个vector_store_id
,你可以retrieve
当前字段值:
client.vector_stores.retrieve(id: vector_store_id)
你可以获取组织下所有可用向量存储的list
:
client.vector_stores.list
你可以修改现有的向量存储,除了file_ids
:
response = client.vector_stores.modify(
id: vector_store_id,
parameters: {
name: "Modified Test Vector Store",
}
)
你可以删除向量存储:
client.vector_stores.delete(id: vector_store_id)
向量存储文件
向量存储文件表示向量存储中的文件。
你可以通过将一个文件附加到向量存储中来创建一个新的向量存储文件。
response = client.vector_store_files.create(
vector_store_id: "vector-store-abc123",
parameters: {
file_id: "file-abc123"
}
)
vector_store_file_id = response["id"]
给定一个vector_store_file_id
,你可以retrieve
当前字段值:
client.vector_store_files.retrieve(
vector_store_id: "vector-store-abc123",
id: vector_store_file_id
)
你可以获取向量存储中所有可用向量存储文件的list
:
client.vector_store_files.list(vector_store_id: "vector-store-abc123")
你可以删除向量存储文件:
client.vector_store_files.delete(
vector_store_id: "vector-store-abc123",
id: vector_store_file_id
)
注意:这将从向量存储中移除文件,但文件本身不会被删除。要删除文件,请使用删除文件端点。
向量存储文件批次
向量存储文件批次表示将多个文件添加到向量存储中的操作。
你可以通过将多个文件附加到向量存储中来创建一个新的向量存储文件批次。
response = client.vector_store_file_batches.create(
vector_store_id: "vector-store-abc123",
parameters: {
file_ids: ["file-abc123", "file-def456"]
}
)
file_batch_id = response["id"]
给定一个file_batch_id
,你可以retrieve
当前字段值:
client.vector_store_file_batches.retrieve(
vector_store_id: "vector-store-abc123",
id: file_batch_id
)
你可以获取向量存储中批次内所有可用文件的list
:
client.vector_store_file_batches.list(
vector_store_id: "vector-store-abc123",
id: file_batch_id
)
你可以取消向量存储文件批次(这会尽快尝试取消批次中文件的处理):
client.vector_store_file_batches.cancel(
vector_store_id: "vector-store-abc123",
id: file_batch_id
)
助理
助理是有状态的参与者,可以进行多次对话并使用工具执行任务(参见助理概述)。
要创建一个新的助理:
response = client.assistants.create(
parameters: {
model: "gpt-4o",
name: "OpenAI-Ruby test assistant",
description: nil,
instructions: "You are a Ruby dev bot. When asked a question, write and run Ruby code to answer the question",
tools: [
{ type: "code_interpreter" },
{ type: "file_search" }
],
tool_resources: {
code_interpreter: {
file_ids: [] # 参见上文中的文件部分以了解如何上传文件
},
file_search: {
vector_store_ids: [] # 参见上文中的向量存储部分以了解如何添加向量存储
}
},
"metadata": { my_internal_version_id: "1.0.0" }
})
assistant_id = response["id"]
给定一个assistant_id
,你可以retrieve
当前字段值:
client.assistants.retrieve(id: assistant_id)
你可以获取组织下所有可用助理的list
:
client.assistants.list
你可以使用助理的ID修改现有的助理(参见API文档):
response = client.assistants.modify(
id: assistant_id,
parameters: {
name: "Modified Test Assistant for OpenAI-Ruby",
metadata: { my_internal_version_id: '1.0.1' }
})
你可以删除助理:
client.assistants.delete(id: assistant_id)
线程和消息
一旦你按照上述步骤创建了一个助理,你需要为助理准备一个Thread
的Messages
来处理(参见助理简介)。例如,作为初始设置,你可以这样做:
# 创建线程
response = client.threads.create # 注意:一旦你创建了一个线程,目前(截至2023-12-10)没有办法列出或恢复它。所以要保存好`id`
thread_id = response["id"]
# 添加用户的初始消息(参见 https://platform.openai.com/docs/api-reference/messages/createMessage)
message_id = client.messages.create(
thread_id: thread_id,
parameters: {
role: "user", # 手动创建的消息必填
content: "Can you help me write an API library to interact with the OpenAI API please?"
})["id"]
# 检索单个消息
message = client.messages.retrieve(thread_id: thread_id, id: message_id)
# 查看线程中的所有消息
messages = client.messages.list(thread_id: thread_id)
在线程不再需要后进行清理:
# 删除线程(及所有相关的消息):
client.threads.delete(id: thread_id)
client.messages.retrieve(thread_id: thread_id, id: message_id) # -> 线程删除后会失败
运行
要提交一个线程以评估助理的模型,请按如下方式创建一个Run
:
# 创建运行(将使用助理定义中的指令/模型/工具)
response = client.runs.create(thread_id: thread_id,
parameters: {
assistant_id: assistant_id,
max_prompt_tokens: 256,
max_completion_tokens: 16
})
run_id = response['id']
你可以流式传输消息块:
client.runs.create(thread_id: thread_id,
parameters: {
assistant_id: assistant_id,
max_prompt_tokens: 256,
max_completion_tokens: 16,
stream: proc do |chunk, _bytesize|
print chunk.dig("delta", "content", 0, "text", "value") if chunk["object"] == "thread.message.delta"
end
})
获取运行状态:
response = client.runs.retrieve(id: run_id, thread_id: thread_id)
status = response['status']
status
响应可以包含以下字符串:queued
、in_progress
、requires_action
、cancelling
、cancelled
、failed
、completed
或expired
,你可以按如下方式处理:
while true do
response = client.runs.retrieve(id: run_id, thread_id: thread_id)
status = response['status']
case status
when 'queued', 'in_progress', 'cancelling'
puts 'Sleeping'
sleep 1 # 等待一秒钟,然后再次轮询
when 'completed'
break # 退出循环并向用户报告结果
填充图像的透明部分,或上传带有透明部分的蒙版,以指示可以根据您的提示更改的图像部分...
```ruby
response = client.images.edit(parameters: { prompt: "在蓝色背景上的一颗红宝石", image: "image.png", mask: "mask.png" })
puts response.dig("data", 0, "url")
# => "https://oaidalleapiprodscus.blob.core.windows.net/private/org-Rf437IxKhh..."
图像变体
创建 n 个图像变体。
response = client.images.variations(parameters: { image: "image.png", n: 2 })
puts response.dig("data", 0, "url")
# => "https://oaidalleapiprodscus.blob.core.windows.net/private/org-Rf437IxKhh..."
内容审核
传递一个字符串以检查它是否违反了OpenAI的内容政策:
response = client.moderations(parameters: { input: "我对此感到担忧。" })
puts response.dig("results", 0, "category_scores", "hate")
# => 5.505014632944949e-05
Whisper
Whisper 是一个语音转文本模型,可用于根据音频文件生成文本:
翻译
翻译 API 以支持语言的音频文件作为输入,并将音频转录为英语。
response = client.audio.translate(
parameters: {
model: "whisper-1",
file: File.open("path_to_file", "rb"),
})
puts response["text"]
# => "文本翻译"
转录
转录 API 接受您想要转录的音频文件并返回所需输出文件格式的文本。
您可以传递音频文件的语言以提高转录质量。支持的语言列表请参考这里。您需要提供语言的 ISO-639-1 代码,例如英语的 "en" 或尼泊尔语的 "ne"。您可以在这里查找代码。
response = client.audio.transcribe(
parameters: {
model: "whisper-1",
file: File.open("path_to_file", "rb"),
language: "en" # 可选项
})
puts response["text"]
# => "文本转录"
语音
语音 API 以文本和声音作为输入,并返回您可以收听的音频文件内容。
response = client.audio.speech(
parameters: {
model: "tts-1",
input: "这是一个语音测试!",
voice: "alloy",
response_format: "mp3", # 可选项
speed: 1.0 # 可选项
}
)
File.binwrite('demo.mp3', response)
# => 播放的 mp3 文件:"这是一个语音测试!"
错误处理
HTTP 错误可以这样捕获:
begin
OpenAI::Client.new.models.retrieve(id: "gpt-4o")
rescue Faraday::Error => e
raise "遇到 Faraday 错误:#{e}"
end
开发
检出仓库后,运行 bin/setup
来安装依赖项。您可以运行 bin/console
以获得一个交互式提示符,允许您进行实验。
要将这个 gem 安装到您的本地机器上,运行 bundle exec rake install
。
要运行所有测试,执行命令 bundle exec rake
,它还将运行代码检查工具(Rubocop)。这个仓库使用 VCR 来记录 API 请求。
[!WARNING] 如果您的环境中有
OPENAI_ACCESS_TOKEN
,运行测试时将使用它来对实际 API 进行测试,这将会很慢并且花费您金钱 - 2 美分或更多!如果您只想对存储的 VCR 响应运行测试,可以使用unset
或类似方法从环境中移除它。
发布
首先,在没有 VCR 的情况下运行测试,以便它们实际访问 API。这将花费 2 美分或更多。在您的环境中设置 OPENAI_ACCESS_TOKEN,或者像这样传递它:
OPENAI_ACCESS_TOKEN=123abc bundle exec rspec
然后更新 version.rb
中的版本号,更新 CHANGELOG.md
,运行 bundle install
以更新 Gemfile.lock,然后运行 bundle exec rake release
,这将为版本创建一个 git 标签,推送 git 提交和标签,并将 .gem
文件推送到 rubygems.org。
贡献
在 GitHub 的 https://github.com/alexrudall/ruby-openai 上欢迎错误报告和拉取请求。该项目旨在成为一个安全、欢迎的合作空间,参与者应遵守行为准则。
许可证
该 gem 作为开源软件可根据 MIT 许可证 的条款使用。
行为准则
在 Ruby OpenAI 项目的代码库、问题跟踪器、聊天室和邮件列表中互动的所有人都应遵守行为准则。