From 1209c3c53f5c534ccf2d05d3ce59a191467d952b Mon Sep 17 00:00:00 2001 From: Reion19 Date: Thu, 10 Jul 2025 16:27:47 +0300 Subject: [PATCH 1/2] add deepseek --- lib/rubyai.rb | 1 + lib/rubyai/configuration.rb | 2 ++ lib/rubyai/provider.rb | 3 ++- lib/rubyai/providers/deepseek.rb | 34 ++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 lib/rubyai/providers/deepseek.rb diff --git a/lib/rubyai.rb b/lib/rubyai.rb index f694941..02cad96 100644 --- a/lib/rubyai.rb +++ b/lib/rubyai.rb @@ -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" diff --git a/lib/rubyai/configuration.rb b/lib/rubyai/configuration.rb index b500124..9b9785f 100644 --- a/lib/rubyai/configuration.rb +++ b/lib/rubyai/configuration.rb @@ -4,6 +4,7 @@ class Configuration :anthropic, :gemini, :bedrock_anthropic, + :deepseek, :api, :model, :messages, @@ -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) diff --git a/lib/rubyai/provider.rb b/lib/rubyai/provider.rb index 8eee905..acc8138 100644 --- a/lib/rubyai/provider.rb +++ b/lib/rubyai/provider.rb @@ -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) diff --git a/lib/rubyai/providers/deepseek.rb b/lib/rubyai/providers/deepseek.rb new file mode 100644 index 0000000..eed3981 --- /dev/null +++ b/lib/rubyai/providers/deepseek.rb @@ -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 \ No newline at end of file From 376c4570d0120a5af177316ae0fe262332c333fb Mon Sep 17 00:00:00 2001 From: Reion19 Date: Thu, 10 Jul 2025 16:30:13 +0300 Subject: [PATCH 2/2] added specs for deepseek provider --- spec/rubyai/providers/deepseek_spec.rb | 247 +++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 spec/rubyai/providers/deepseek_spec.rb diff --git a/spec/rubyai/providers/deepseek_spec.rb b/spec/rubyai/providers/deepseek_spec.rb new file mode 100644 index 0000000..63d9c5c --- /dev/null +++ b/spec/rubyai/providers/deepseek_spec.rb @@ -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 \ No newline at end of file