llm.rb 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +83 -22
  3. data/lib/llm/conversation.rb +14 -2
  4. data/lib/llm/core_ext/ostruct.rb +0 -0
  5. data/lib/llm/error.rb +0 -0
  6. data/lib/llm/file.rb +0 -0
  7. data/lib/llm/http_client.rb +0 -0
  8. data/lib/llm/lazy_conversation.rb +14 -2
  9. data/lib/llm/message.rb +1 -1
  10. data/lib/llm/message_queue.rb +0 -0
  11. data/lib/llm/model.rb +7 -0
  12. data/lib/llm/provider.rb +117 -98
  13. data/lib/llm/providers/anthropic/error_handler.rb +1 -1
  14. data/lib/llm/providers/anthropic/format.rb +0 -0
  15. data/lib/llm/providers/anthropic/response_parser.rb +0 -0
  16. data/lib/llm/providers/anthropic.rb +31 -15
  17. data/lib/llm/providers/gemini/error_handler.rb +0 -0
  18. data/lib/llm/providers/gemini/format.rb +0 -0
  19. data/lib/llm/providers/gemini/response_parser.rb +0 -0
  20. data/lib/llm/providers/gemini.rb +25 -14
  21. data/lib/llm/providers/ollama/error_handler.rb +0 -0
  22. data/lib/llm/providers/ollama/format.rb +0 -0
  23. data/lib/llm/providers/ollama/response_parser.rb +13 -0
  24. data/lib/llm/providers/ollama.rb +32 -8
  25. data/lib/llm/providers/openai/error_handler.rb +0 -0
  26. data/lib/llm/providers/openai/format.rb +0 -0
  27. data/lib/llm/providers/openai/response_parser.rb +5 -3
  28. data/lib/llm/providers/openai.rb +22 -12
  29. data/lib/llm/providers/voyageai/error_handler.rb +32 -0
  30. data/lib/llm/providers/voyageai/response_parser.rb +13 -0
  31. data/lib/llm/providers/voyageai.rb +44 -0
  32. data/lib/llm/response/completion.rb +0 -0
  33. data/lib/llm/response/embedding.rb +0 -0
  34. data/lib/llm/response.rb +0 -0
  35. data/lib/llm/version.rb +1 -1
  36. data/lib/llm.rb +18 -8
  37. data/llm.gemspec +6 -1
  38. data/share/llm/models/anthropic.yml +35 -0
  39. data/share/llm/models/gemini.yml +35 -0
  40. data/share/llm/models/ollama.yml +155 -0
  41. data/share/llm/models/openai.yml +46 -0
  42. data/spec/anthropic/completion_spec.rb +11 -27
  43. data/spec/anthropic/embedding_spec.rb +25 -0
  44. data/spec/gemini/completion_spec.rb +13 -29
  45. data/spec/gemini/embedding_spec.rb +4 -12
  46. data/spec/llm/lazy_conversation_spec.rb +45 -63
  47. data/spec/ollama/completion_spec.rb +7 -16
  48. data/spec/ollama/embedding_spec.rb +14 -5
  49. data/spec/openai/completion_spec.rb +19 -43
  50. data/spec/openai/embedding_spec.rb +4 -12
  51. data/spec/readme_spec.rb +9 -12
  52. data/spec/setup.rb +7 -16
  53. metadata +81 -2
@@ -3,29 +3,11 @@
3
3
  require "setup"
4
4
 
5
5
  RSpec.describe "LLM::Anthropic: completions" do
6
- subject(:anthropic) { LLM.anthropic("") }
6
+ subject(:anthropic) { LLM.anthropic(token) }
7
+ let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
7
8
 
8
- before(:each, :success) do
9
- stub_request(:post, "https://api.anthropic.com/v1/messages")
10
- .with(headers: {"Content-Type" => "application/json"})
11
- .to_return(
12
- status: 200,
13
- body: fixture("anthropic/completions/ok_completion.json"),
14
- headers: {"Content-Type" => "application/json"}
15
- )
16
- end
17
-
18
- before(:each, :unauthorized) do
19
- stub_request(:post, "https://api.anthropic.com/v1/messages")
20
- .with(headers: {"Content-Type" => "application/json"})
21
- .to_return(
22
- status: 403,
23
- body: fixture("anthropic/completions/unauthorized_completion.json"),
24
- headers: {"Content-Type" => "application/json"}
25
- )
26
- end
27
-
28
- context "when given a successful response", :success do
9
+ context "when given a successful response",
10
+ vcr: {cassette_name: "anthropic/completions/successful_response"} do
29
11
  subject(:response) { anthropic.complete("Hello, world", :user) }
30
12
 
31
13
  it "returns a completion" do
@@ -38,9 +20,9 @@ RSpec.describe "LLM::Anthropic: completions" do
38
20
 
39
21
  it "includes token usage" do
40
22
  expect(response).to have_attributes(
41
- prompt_tokens: 2095,
42
- completion_tokens: 503,
43
- total_tokens: 2598
23
+ prompt_tokens: 10,
24
+ completion_tokens: 30,
25
+ total_tokens: 40
44
26
  )
45
27
  end
46
28
 
@@ -50,7 +32,7 @@ RSpec.describe "LLM::Anthropic: completions" do
50
32
  it "has choices" do
51
33
  expect(choice).to have_attributes(
52
34
  role: "assistant",
53
- content: "Hi! My name is Claude."
35
+ content: "Hello! How can I assist you today? Feel free to ask me any questions or let me know if you need help with anything."
54
36
  )
55
37
  end
56
38
 
@@ -60,8 +42,10 @@ RSpec.describe "LLM::Anthropic: completions" do
60
42
  end
61
43
  end
62
44
 
63
- context "when given an unauthorized response", :unauthorized do
45
+ context "when given an unauthorized response",
46
+ vcr: {cassette_name: "anthropic/completions/unauthorized_response"} do
64
47
  subject(:response) { anthropic.complete("Hello", :user) }
48
+ let(:token) { "BADTOKEN" }
65
49
 
66
50
  it "raises an error" do
67
51
  expect { response }.to raise_error(LLM::Error::Unauthorized)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "setup"
4
+
5
+ RSpec.describe "LLM::Anthropic: embeddings" do
6
+ let(:anthropic) { LLM.anthropic(token) }
7
+ let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
8
+
9
+ context "when given a successful response",
10
+ vcr: {cassette_name: "anthropic/embeddings/successful_response"} do
11
+ subject(:response) { anthropic.embed("Hello, world", token:) }
12
+
13
+ it "returns an embedding" do
14
+ expect(response).to be_instance_of(LLM::Response::Embedding)
15
+ end
16
+
17
+ it "returns a model" do
18
+ expect(response.model).to eq("voyage-2")
19
+ end
20
+
21
+ it "has embeddings" do
22
+ expect(response.embeddings).to be_instance_of(Array)
23
+ end
24
+ end
25
+ end
@@ -3,44 +3,26 @@
3
3
  require "setup"
4
4
 
5
5
  RSpec.describe "LLM::Gemini: completions" do
6
- subject(:gemini) { LLM.gemini("") }
6
+ subject(:gemini) { LLM.gemini(token) }
7
+ let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
7
8
 
8
- before(:each, :success) do
9
- stub_request(:post, "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=")
10
- .with(headers: {"Content-Type" => "application/json"})
11
- .to_return(
12
- status: 200,
13
- body: fixture("gemini/completions/ok_completion.json"),
14
- headers: {"Content-Type" => "application/json"}
15
- )
16
- end
17
-
18
- before(:each, :unauthorized) do
19
- stub_request(:post, "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=")
20
- .with(headers: {"Content-Type" => "application/json"})
21
- .to_return(
22
- status: 400,
23
- body: fixture("gemini/completions/unauthorized_completion.json"),
24
- headers: {"Content-Type" => "application/json"}
25
- )
26
- end
27
-
28
- context "when given a successful response", :success do
29
- subject(:response) { gemini.complete(LLM::Message.new("user", "Hello!")) }
9
+ context "when given a successful response",
10
+ vcr: {cassette_name: "gemini/completions/successful_response"} do
11
+ subject(:response) { gemini.complete("Hello!", :user) }
30
12
 
31
13
  it "returns a completion" do
32
14
  expect(response).to be_a(LLM::Response::Completion)
33
15
  end
34
16
 
35
17
  it "returns a model" do
36
- expect(response.model).to eq("gemini-1.5-flash-001")
18
+ expect(response.model).to eq("gemini-1.5-flash")
37
19
  end
38
20
 
39
21
  it "includes token usage" do
40
22
  expect(response).to have_attributes(
41
23
  prompt_tokens: 2,
42
- completion_tokens: 10,
43
- total_tokens: 12
24
+ completion_tokens: 11,
25
+ total_tokens: 13
44
26
  )
45
27
  end
46
28
 
@@ -52,7 +34,7 @@ RSpec.describe "LLM::Gemini: completions" do
52
34
  choices: [
53
35
  have_attributes(
54
36
  role: "model",
55
- content: "Hello! How can I help you today? \n"
37
+ content: "Hello there! How can I help you today?\n"
56
38
  )
57
39
  ]
58
40
  )
@@ -64,8 +46,10 @@ RSpec.describe "LLM::Gemini: completions" do
64
46
  end
65
47
  end
66
48
 
67
- context "when given an unauthorized response", :unauthorized do
68
- subject(:response) { gemini.complete(LLM::Message.new("user", "Hello!")) }
49
+ context "when given an unauthorized response",
50
+ vcr: {cassette_name: "gemini/completions/unauthorized_response"} do
51
+ subject(:response) { gemini.complete("Hello!", :user) }
52
+ let(:token) { "BADTOKEN" }
69
53
 
70
54
  it "raises an error" do
71
55
  expect { response }.to raise_error(LLM::Error::Unauthorized)
@@ -3,19 +3,11 @@
3
3
  require "setup"
4
4
 
5
5
  RSpec.describe "LLM::OpenAI: embeddings" do
6
- let(:gemini) { LLM.gemini("") }
6
+ let(:gemini) { LLM.gemini(token) }
7
+ let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
7
8
 
8
- before(:each, :success) do
9
- stub_request(:post, "https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent?key=")
10
- .with(headers: {"Content-Type" => "application/json"})
11
- .to_return(
12
- status: 200,
13
- body: fixture("gemini/embeddings/hello_world_embedding.json"),
14
- headers: {"Content-Type" => "application/json"}
15
- )
16
- end
17
-
18
- context "when given a successful response", :success do
9
+ context "when given a successful response",
10
+ vcr: {cassette_name: "gemini/embeddings/successful_response"} do
19
11
  subject(:response) { gemini.embed("Hello, world") }
20
12
 
21
13
  it "returns an embedding" do
@@ -3,106 +3,88 @@
3
3
  require "setup"
4
4
 
5
5
  RSpec.describe LLM::LazyConversation do
6
- context "with gemini" do
7
- let(:provider) { LLM.gemini("") }
6
+ let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
7
+ let(:prompt) { "Keep your answers short and concise, and provide three answers to the three questions" }
8
+
9
+ context "with gemini",
10
+ vcr: {cassette_name: "gemini/lazy_conversation/successful_response"} do
11
+ let(:provider) { LLM.gemini(token) }
8
12
  let(:conversation) { described_class.new(provider) }
9
13
 
10
14
  context "when given a thread of messages" do
11
15
  subject(:message) { conversation.messages.to_a[-1] }
12
16
 
13
17
  before do
14
- stub_request(:post, "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=")
15
- .with(
16
- headers: {"Content-Type" => "application/json"},
17
- body: request_fixture("gemini/completions/ok_completion.json")
18
- )
19
- .to_return(
20
- status: 200,
21
- body: response_fixture("gemini/completions/ok_completion.json"),
22
- headers: {"Content-Type" => "application/json"}
23
- )
24
- end
25
-
26
- before do
27
- conversation.chat "Hello"
28
- conversation.chat "I have a question"
29
- conversation.chat "How are you?"
18
+ conversation.chat prompt
19
+ conversation.chat "What is 3+2 ?"
20
+ conversation.chat "What is 5+5 ?"
21
+ conversation.chat "What is 5+7 ?"
30
22
  end
31
23
 
32
24
  it "maintains a conversation" do
33
- expect(message).to have_attributes(
25
+ is_expected.to have_attributes(
34
26
  role: "model",
35
- content: "Hello! How can I help you today? \n"
27
+ content: "5\n10\n12\n"
36
28
  )
37
29
  end
38
30
  end
39
31
  end
40
32
 
41
- context "with openai" do
42
- let(:provider) { LLM.openai("") }
33
+ context "with openai" do
34
+ let(:provider) { LLM.openai(token) }
43
35
  let(:conversation) { described_class.new(provider) }
44
36
 
45
- context "when given a thread of messages" do
46
- subject(:message) { conversation.messages.to_a[-1] }
47
-
48
- before do
49
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
50
- .with(
51
- headers: {"Content-Type" => "application/json"},
52
- body: request_fixture("openai/completions/ok_completion.json")
53
- )
54
- .to_return(
55
- status: 200,
56
- body: response_fixture("openai/completions/ok_completion.json"),
57
- headers: {"Content-Type" => "application/json"}
58
- )
59
- end
37
+ context "when given a thread of messages",
38
+ vcr: {cassette_name: "openai/lazy_conversation/successful_response"} do
39
+ subject(:message) { conversation.recent_message }
60
40
 
61
41
  before do
62
- conversation.chat "Hello"
63
- conversation.chat "I have a question"
64
- conversation.chat "How are you?"
42
+ conversation.chat prompt, :system
43
+ conversation.chat "What is 3+2 ?"
44
+ conversation.chat "What is 5+5 ?"
45
+ conversation.chat "What is 5+7 ?"
65
46
  end
66
47
 
67
48
  it "maintains a conversation" do
68
- expect(message).to have_attributes(
49
+ is_expected.to have_attributes(
69
50
  role: "assistant",
70
- content: "Hello! How can I assist you today?"
51
+ content: "1. 5 \n2. 10 \n3. 12 "
71
52
  )
72
53
  end
73
54
  end
55
+
56
+ context "when given a specific model",
57
+ vcr: {cassette_name: "openai/lazy_conversation/successful_response_o3_mini"} do
58
+ let(:conversation) { described_class.new(provider, model: provider.models["o3-mini"]) }
59
+
60
+ it "maintains the model throughout a conversation" do
61
+ conversation.chat(prompt, :system)
62
+ expect(conversation.recent_message.extra[:completion].model).to eq("o3-mini-2025-01-31")
63
+ conversation.chat("What is 5+5?")
64
+ expect(conversation.recent_message.extra[:completion].model).to eq("o3-mini-2025-01-31")
65
+ end
66
+ end
74
67
  end
75
68
 
76
- context "with ollama" do
77
- let(:provider) { LLM.ollama("") }
69
+ context "with ollama",
70
+ vcr: {cassette_name: "ollama/lazy_conversation/successful_response"} do
71
+ let(:provider) { LLM.ollama(nil, host: "eel.home.network") }
78
72
  let(:conversation) { described_class.new(provider) }
79
73
 
80
74
  context "when given a thread of messages" do
81
- subject(:message) { conversation.messages.to_a[-1] }
82
-
83
- before do
84
- stub_request(:post, "http://localhost:11434/api/chat")
85
- .with(
86
- headers: {"Content-Type" => "application/json"},
87
- body: request_fixture("ollama/completions/ok_completion.json")
88
- )
89
- .to_return(
90
- status: 200,
91
- body: response_fixture("ollama/completions/ok_completion.json"),
92
- headers: {"Content-Type" => "application/json"}
93
- )
94
- end
75
+ subject(:message) { conversation.recent_message }
95
76
 
96
77
  before do
97
- conversation.chat "Hello"
98
- conversation.chat "I have a question"
99
- conversation.chat "How are you?"
78
+ conversation.chat prompt, :system
79
+ conversation.chat "What is 3+2 ?"
80
+ conversation.chat "What is 5+5 ?"
81
+ conversation.chat "What is 5+7 ?"
100
82
  end
101
83
 
102
84
  it "maintains a conversation" do
103
- expect(message).to have_attributes(
85
+ is_expected.to have_attributes(
104
86
  role: "assistant",
105
- content: "Hello! How are you today?"
87
+ content: "Here are the calculations:\n\n1. 3 + 2 = 5\n2. 5 + 5 = 10\n3. 5 + 7 = 12"
106
88
  )
107
89
  end
108
90
  end
@@ -3,19 +3,10 @@
3
3
  require "setup"
4
4
 
5
5
  RSpec.describe "LLM::Ollama: completions" do
6
- subject(:ollama) { LLM.ollama("") }
7
-
8
- before(:each, :success) do
9
- stub_request(:post, "localhost:11434/api/chat")
10
- .with(headers: {"Content-Type" => "application/json"})
11
- .to_return(
12
- status: 200,
13
- body: fixture("ollama/completions/ok_completion.json"),
14
- headers: {"Content-Type" => "application/json"}
15
- )
16
- end
6
+ let(:ollama) { LLM.ollama(nil, host: "eel.home.network") }
17
7
 
18
- context "when given a successful response", :success do
8
+ context "when given a successful response",
9
+ vcr: {cassette_name: "ollama/completions/successful_response"} do
19
10
  subject(:response) { ollama.complete("Hello!", :user) }
20
11
 
21
12
  it "returns a completion" do
@@ -28,9 +19,9 @@ RSpec.describe "LLM::Ollama: completions" do
28
19
 
29
20
  it "includes token usage" do
30
21
  expect(response).to have_attributes(
31
- prompt_tokens: 26,
32
- completion_tokens: 298,
33
- total_tokens: 324
22
+ prompt_tokens: 27,
23
+ completion_tokens: 26,
24
+ total_tokens: 53
34
25
  )
35
26
  end
36
27
 
@@ -40,7 +31,7 @@ RSpec.describe "LLM::Ollama: completions" do
40
31
  it "has choices" do
41
32
  expect(choice).to have_attributes(
42
33
  role: "assistant",
43
- content: "Hello! How are you today?"
34
+ content: "Hello! It's nice to meet you. Is there something I can help you with, or would you like to chat?"
44
35
  )
45
36
  end
46
37
 
@@ -3,13 +3,22 @@
3
3
  require "setup"
4
4
 
5
5
  RSpec.describe "LLM::Ollama: embeddings" do
6
- let(:ollama) { LLM.ollama("") }
6
+ let(:ollama) { LLM.ollama(nil, host: "eel.home.network") }
7
7
 
8
- context "when given a successful response", :success do
9
- subject(:response) { ollama.embed("Hello, world") }
8
+ context "when given a successful response",
9
+ vcr: {cassette_name: "ollama/embeddings/successful_response"} do
10
+ subject(:response) { ollama.embed(["This is a paragraph", "This is another one"]) }
10
11
 
11
- it "raises NotImplementedError" do
12
- expect { response }.to raise_error(NotImplementedError)
12
+ it "returns an embedding" do
13
+ expect(response).to be_instance_of(LLM::Response::Embedding)
14
+ end
15
+
16
+ it "returns a model" do
17
+ expect(response.model).to eq("llama3.2")
18
+ end
19
+
20
+ it "has embeddings" do
21
+ expect(response.embeddings.size).to eq(2)
13
22
  end
14
23
  end
15
24
  end
@@ -3,38 +3,11 @@
3
3
  require "setup"
4
4
 
5
5
  RSpec.describe "LLM::OpenAI: completions" do
6
- subject(:openai) { LLM.openai("") }
6
+ subject(:openai) { LLM.openai(token) }
7
+ let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
7
8
 
8
- before(:each, :success) do
9
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
10
- .with(headers: {"Content-Type" => "application/json"})
11
- .to_return(
12
- status: 200,
13
- body: fixture("openai/completions/ok_completion.json"),
14
- headers: {"Content-Type" => "application/json"}
15
- )
16
- end
17
-
18
- before(:each, :unauthorized) do
19
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
20
- .with(headers: {"Content-Type" => "application/json"})
21
- .to_return(
22
- status: 401,
23
- body: fixture("openai/completions/unauthorized_completion.json"),
24
- headers: {"Content-Type" => "application/json"}
25
- )
26
- end
27
-
28
- before(:each, :bad_request) do
29
- stub_request(:post, "https://api.openai.com/v1/chat/completions")
30
- .with(headers: {"Content-Type" => "application/json"})
31
- .to_return(
32
- status: 400,
33
- body: fixture("openai/completions/badrequest_completion.json")
34
- )
35
- end
36
-
37
- context "when given a successful response", :success do
9
+ context "when given a successful response",
10
+ vcr: {cassette_name: "openai/completions/successful_response"} do
38
11
  subject(:response) { openai.complete("Hello!", :user) }
39
12
 
40
13
  it "returns a completion" do
@@ -48,8 +21,8 @@ RSpec.describe "LLM::OpenAI: completions" do
48
21
  it "includes token usage" do
49
22
  expect(response).to have_attributes(
50
23
  prompt_tokens: 9,
51
- completion_tokens: 9,
52
- total_tokens: 18
24
+ completion_tokens: 10,
25
+ total_tokens: 19
53
26
  )
54
27
  end
55
28
 
@@ -69,31 +42,34 @@ RSpec.describe "LLM::OpenAI: completions" do
69
42
  end
70
43
  end
71
44
 
72
- context "when given an unauthorized response", :unauthorized do
73
- subject(:response) { openai.complete(LLM::Message.new("Hello!", :user)) }
45
+ context "when given a 'bad request' response",
46
+ vcr: {cassette_name: "openai/completions/bad_request"} do
47
+ subject(:response) { openai.complete(URI("/foobar.exe"), :user) }
74
48
 
75
49
  it "raises an error" do
76
- expect { response }.to raise_error(LLM::Error::Unauthorized)
50
+ expect { response }.to raise_error(LLM::Error::BadResponse)
77
51
  end
78
52
 
79
53
  it "includes the response" do
80
54
  response
81
- rescue LLM::Error::Unauthorized => ex
82
- expect(ex.response).to be_kind_of(Net::HTTPResponse)
55
+ rescue LLM::Error => ex
56
+ expect(ex.response).to be_instance_of(Net::HTTPBadRequest)
83
57
  end
84
58
  end
85
59
 
86
- context "when given a 'bad request' response", :bad_request do
87
- subject(:response) { openai.complete(URI("/foobar.exe"), :user) }
60
+ context "when given an unauthorized response",
61
+ vcr: {cassette_name: "openai/completions/unauthorized_response"} do
62
+ subject(:response) { openai.complete(LLM::Message.new("Hello!", :user)) }
63
+ let(:token) { "BADTOKEN" }
88
64
 
89
65
  it "raises an error" do
90
- expect { response }.to raise_error(LLM::Error::BadResponse)
66
+ expect { response }.to raise_error(LLM::Error::Unauthorized)
91
67
  end
92
68
 
93
69
  it "includes the response" do
94
70
  response
95
- rescue LLM::Error => ex
96
- expect(ex.response).to be_instance_of(Net::HTTPBadRequest)
71
+ rescue LLM::Error::Unauthorized => ex
72
+ expect(ex.response).to be_kind_of(Net::HTTPResponse)
97
73
  end
98
74
  end
99
75
  end
@@ -3,19 +3,11 @@
3
3
  require "setup"
4
4
 
5
5
  RSpec.describe "LLM::OpenAI: embeddings" do
6
- let(:openai) { LLM.openai("") }
6
+ let(:openai) { LLM.openai(token) }
7
+ let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
7
8
 
8
- before(:each, :success) do
9
- stub_request(:post, "https://api.openai.com/v1/embeddings")
10
- .with(headers: {"Content-Type" => "application/json"})
11
- .to_return(
12
- status: 200,
13
- body: fixture("openai/embeddings/hello_world_embedding.json"),
14
- headers: {"Content-Type" => "application/json"}
15
- )
16
- end
17
-
18
- context "when given a successful response", :success do
9
+ context "when given a successful response",
10
+ vcr: {cassette_name: "openai/embeddings/successful_response"} do
19
11
  subject(:response) { openai.embed("Hello, world") }
20
12
 
21
13
  it "returns an embedding" do
data/spec/readme_spec.rb CHANGED
@@ -22,22 +22,19 @@ RSpec.describe "The README examples" do
22
22
 
23
23
  let(:expected_conversation) do
24
24
  [
25
- "[system] You are a friendly chatbot. Sometimes, you like to tell a joke.",
26
- "But the joke must be based on the given inputs.",
25
+ "[system] You are my math assistant.",
26
+ "I will provide you with (simple) equations.",
27
+ "You will provide answers in the format \"The answer to <equation> is <answer>\".",
27
28
  "I will provide you a set of messages. Reply to all of them.",
28
29
  "A message is considered unanswered if there is no corresponding assistant response.",
29
30
 
30
- "[user] What color is the sky?",
31
- "[user] What color is an orange?",
32
- "[user] I like Ruby",
31
+ "[user] Tell me the answer to 5 + 15",
32
+ "[user] Tell me the answer to (5 + 15) * 2",
33
+ "[user] Tell me the answer to ((5 + 15) * 2) / 10",
33
34
 
34
- "[assistant] The sky is typically blue during the day, but it can have beautiful",
35
- "hues of pink, orange, and purple during sunset! As for an orange,",
36
- "it's typically orange in color - funny how that works, right?",
37
- "I love Ruby too! Did you know that a Ruby is not only a beautiful",
38
- "gemstone, but it's also a programming language that's both elegant",
39
- "and powerful! Speaking of colors, why did the orange stop?",
40
- "Because it ran out of juice!"
35
+ "[assistant] The answer to 5 + 15 is 20.",
36
+ "The answer to (5 + 15) * 2 is 40.",
37
+ "The answer to ((5 + 15) * 2) / 10 is 4."
41
38
  ].map(&:strip)
42
39
  end
43
40
 
data/spec/setup.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "llm"
4
4
  require "webmock/rspec"
5
+ require "vcr"
5
6
 
6
7
  RSpec.configure do |config|
7
8
  config.disable_monkey_patching!
@@ -9,21 +10,11 @@ RSpec.configure do |config|
9
10
  config.expect_with :rspec do |c|
10
11
  c.syntax = :expect
11
12
  end
13
+ end
12
14
 
13
- config.include Module.new {
14
- def request_fixture(file)
15
- path = File.join(fixtures, "requests", file)
16
- File.read(path).chomp
17
- end
18
-
19
- def response_fixture(file)
20
- path = File.join(fixtures, "responses", file)
21
- File.read(path).chomp
22
- end
23
- alias_method :fixture, :response_fixture
24
-
25
- def fixtures
26
- File.join(__dir__, "fixtures")
27
- end
28
- }
15
+ VCR.configure do |config|
16
+ config.cassette_library_dir = "spec/fixtures/cassettes"
17
+ config.hook_into :webmock
18
+ config.configure_rspec_metadata!
19
+ config.filter_sensitive_data("TOKEN") { ENV["LLM_SECRET"] }
29
20
  end