Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rubyai.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require_relative "rubyai/providers/anthropic"
require_relative "rubyai/providers/gemini"
require_relative "rubyai/providers/bedrock_anthropic"
require_relative "rubyai/providers/deepseek"
require_relative "rubyai/provider"
require_relative "rubyai/configuration"
require_relative "rubyai/http"
Expand Down
2 changes: 2 additions & 0 deletions lib/rubyai/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Configuration
:anthropic,
:gemini,
:bedrock_anthropic,
:deepseek,
:api,
:model,
:messages,
Expand Down Expand Up @@ -47,6 +48,7 @@ def set_instances_of_providers
@anthropic ||= Providers::Anthropic.new
@gemini ||= Providers::Gemini.new
@bedrock_anthropic ||= Providers::BedrockAnthropic.new
@deepseek ||= Providers::DeepSeek.new
end

def find_provider(config)
Expand Down
3 changes: 2 additions & 1 deletion lib/rubyai/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module Provider
PROVIDERS = {
"openai" => "https://api.openai.com/v1/chat/completions",
"anthropic" => "https://api.anthropic.com/v1/chat/completions",
"gemini" => "https://generativelanguage.googleapis.com/v1beta/models"
"gemini" => "https://generativelanguage.googleapis.com/v1beta/models",
"deepseek" => "https://api.deepseek.com/v1/chat/completions"
}

def PROVIDERS.[](provider, model = nil)
Expand Down
34 changes: 34 additions & 0 deletions lib/rubyai/providers/deepseek.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module RubyAI
module Providers
class DeepSeek < Base
def initialize(api: nil, messages: nil, temperature: 0.7)
@api = api
@messages = messages
@temperature = temperature
end

def models
{
"deepseek-chat" => "deepseek-chat",
"deepseek-coder" => "deepseek-coder",
"deepseek-reasoner" => "deepseek-reasoner",
"deepseek-v3" => "deepseek-v3"
}
end

def build_http_body(messages = nil, model = "deepseek-chat", temperature = nil)
{
model: model,
messages: [{ role: "user", content: messages || @messages }],
temperature: temperature || @temperature
}
end

def build_http_headers(_provider)
{
Authorization: "Bearer #{@api || RubyAI.config.deepseek.api}"
}
end
end
end
end
247 changes: 247 additions & 0 deletions spec/rubyai/providers/deepseek_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
require_relative "../../../lib/rubyai"

require "spec_helper"

RSpec.describe RubyAI::Providers::DeepSeek do
let(:api_key) { "sk-test-api-key" }
let(:messages) { "Hello, world!" }
let(:temperature) { 0.8 }

# Mock the configuration
before do
allow(RubyAI).to receive(:config).and_return(
double(deepseek: double(api: "sk-config-api-key"))
)
end

describe "#initialize" do
context "with all parameters" do
subject { described_class.new(api: api_key, messages: messages, temperature: temperature) }

it "sets the api key" do
expect(subject.api).to eq(api_key)
end

it "sets the messages" do
expect(subject.messages).to eq(messages)
end

it "sets the temperature" do
expect(subject.temperature).to eq(temperature)
end
end

context "with default parameters" do
subject { described_class.new }

it "sets api to nil" do
expect(subject.api).to be_nil
end

it "sets messages to nil" do
expect(subject.messages).to be_nil
end

it "sets temperature to default 0.7" do
expect(subject.temperature).to eq(0.7)
end
end

context "with partial parameters" do
subject { described_class.new(api: api_key) }

it "sets the provided api key" do
expect(subject.api).to eq(api_key)
end

it "uses default values for other parameters" do
expect(subject.messages).to be_nil
expect(subject.temperature).to eq(0.7)
end
end
end

describe "#models" do
subject { described_class.new.models }

it "returns a hash of available models" do
expect(subject).to be_a(Hash)
end

it "includes deepseek-chat model" do
expect(subject).to have_key("deepseek-chat")
expect(subject["deepseek-chat"]).to eq("deepseek-chat")
end

it "includes deepseek-coder model" do
expect(subject).to have_key("deepseek-coder")
expect(subject["deepseek-coder"]).to eq("deepseek-coder")
end

it "includes deepseek-reasoner model" do
expect(subject).to have_key("deepseek-reasoner")
expect(subject["deepseek-reasoner"]).to eq("deepseek-reasoner")
end

it "includes deepseek-v3 model" do
expect(subject).to have_key("deepseek-v3")
expect(subject["deepseek-v3"]).to eq("deepseek-v3")
end

it "returns exactly 4 models" do
expect(subject.keys.count).to eq(4)
end
end

describe "#build_http_body" do
let(:provider) { described_class.new(messages: "instance message", temperature: 0.5) }

context "with all parameters provided" do
let(:result) { provider.build_http_body("test message", "deepseek-coder", 0.9) }

it "uses the provided message" do
expect(result[:messages]).to eq([{ role: "user", content: "test message" }])
end

it "uses the provided model" do
expect(result[:model]).to eq("deepseek-coder")
end

it "uses the provided temperature" do
expect(result[:temperature]).to eq(0.9)
end
end

context "with default parameters" do
let(:result) { provider.build_http_body }

it "uses instance message when no message provided" do
expect(result[:messages]).to eq([{ role: "user", content: "instance message" }])
end

it "uses default model when no model provided" do
expect(result[:model]).to eq("deepseek-chat")
end

it "uses instance temperature when no temperature provided" do
expect(result[:temperature]).to eq(0.5)
end
end

context "with partial parameters" do
let(:result) { provider.build_http_body("custom message", "deepseek-v3") }

it "uses provided message and model" do
expect(result[:messages]).to eq([{ role: "user", content: "custom message" }])
expect(result[:model]).to eq("deepseek-v3")
end

it "uses instance temperature when not provided" do
expect(result[:temperature]).to eq(0.5)
end
end

context "when instance has no default message" do
let(:provider) { described_class.new(temperature: 0.3) }
let(:result) { provider.build_http_body }

it "uses nil for message content" do
expect(result[:messages]).to eq([{ role: "user", content: nil }])
end
end

it "always returns a hash with required keys" do
result = provider.build_http_body
expect(result).to have_key(:model)
expect(result).to have_key(:messages)
expect(result).to have_key(:temperature)
end

it "formats messages as an array with role and content" do
result = provider.build_http_body("test")
expect(result[:messages]).to be_an(Array)
expect(result[:messages].first).to have_key(:role)
expect(result[:messages].first).to have_key(:content)
expect(result[:messages].first[:role]).to eq("user")
end
end

describe "#build_http_headers" do
context "when api key is provided in initialization" do
let(:provider) { described_class.new(api: api_key) }
let(:result) { provider.build_http_headers("deepseek") }

it "uses the instance api key" do
expect(result[:Authorization]).to eq("Bearer #{api_key}")
end
end

context "when api key is not provided in initialization" do
let(:provider) { described_class.new }
let(:result) { provider.build_http_headers("deepseek") }

it "uses the config api key" do
expect(result[:Authorization]).to eq("Bearer sk-config-api-key")
end
end

context "when both instance and config api keys are nil" do
let(:provider) { described_class.new }

before do
allow(RubyAI).to receive(:config).and_return(
double(deepseek: double(api: nil))
)
end

it "sets Authorization with Bearer nil" do
result = provider.build_http_headers("deepseek")
expect(result[:Authorization]).to eq("Bearer ")
end
end

it "returns a hash with Authorization key" do
provider = described_class.new(api: api_key)
result = provider.build_http_headers("deepseek")
expect(result).to be_a(Hash)
expect(result).to have_key(:Authorization)
end

it "ignores the provider parameter (unused)" do
provider = described_class.new(api: api_key)
result1 = provider.build_http_headers("deepseek")
result2 = provider.build_http_headers("different_provider")
expect(result1).to eq(result2)
end
end

describe "attribute accessors" do
let(:provider) { described_class.new }

it "allows reading and writing api" do
provider.api = "new-api-key"
expect(provider.api).to eq("new-api-key")
end

it "allows reading and writing messages" do
provider.messages = "new message"
expect(provider.messages).to eq("new message")
end

it "allows reading and writing temperature" do
provider.temperature = 1.0
expect(provider.temperature).to eq(1.0)
end

it "allows reading and writing model" do
provider.model = "custom-model"
expect(provider.model).to eq("custom-model")
end
end

describe "inheritance" do
it "inherits from Base class" do
expect(described_class.superclass.name).to eq("RubyAI::Providers::Base")
end
end
end