langchainrb 0.5.1 → 0.5.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2673339c5bbe874a8bdf1722a2556f26d9fe13394875af914b5203632714f2f0
4
- data.tar.gz: 216ab880c2c6094b267cbf3efcaf19ce74bea7cc665442fbf2b23108a9cb087b
3
+ metadata.gz: d36de4206b792714ba9b6773c03272e9638b14caf7140e0bc00c3e767aa5fdef
4
+ data.tar.gz: 819fab9de55a34e4e6dc865febc19bb9979df55fa8fc6a753774cf1961c40103
5
5
  SHA512:
6
- metadata.gz: 408cf6194d85a4af076adbfd8be4a360d094200d127672f218d8914fbcd67d1a8a803645219532f66d4e79214571d61b629570df071de871b013e2d9d6c0d3a5
7
- data.tar.gz: 123016bd42d1d2539c13f7d68074ddc19dc8a5880ae0b02b103e20bf7f058adfe2659beb90263a852bbf38b4b169622a1b7ac8a245c3791ab9b9ae8f8fc4e3cb
6
+ metadata.gz: 6e180b41bbca96bd5523c276923f223bbebe470314086c6a909df440890793bcc70dbd66ecf59bf5d0fd52426650cc5d2684c56cc8fc643209cc1679527cbef4
7
+ data.tar.gz: af5db76c2b22b5c7bdc1170de437921e8464a16566f46a5cad465d69e6da47c97a82f7331a5ea5747840e58acc71463aa8456b03e9bc8851efda7b734e5d23cc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.2] - 2023-06-07
4
+ - 🗣️ LLMs
5
+ - Auto-calculate the max_tokens: setting to be passed on to OpenAI
6
+
3
7
  ## [0.5.1] - 2023-06-06
4
8
  - 🛠️ Tools
5
9
  - Modified Tool usage. Agents now accept Tools instances instead of Tool strings.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- langchainrb (0.5.1)
4
+ langchainrb (0.5.2)
5
5
  colorize (~> 0.8.1)
6
6
  tiktoken_ruby (~> 0.0.5)
7
7
 
data/README.md CHANGED
@@ -281,11 +281,10 @@ Add `gem "sequel"` to your Gemfile
281
281
  ```ruby
282
282
  database = Langchain::Tool::Database.new(connection_string: "postgres://user:password@localhost:5432/db_name")
283
283
 
284
- agent = Langchain::Agent::SQLQueryAgent.new(llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"]), tools: [database])
285
-
284
+ agent = Langchain::Agent::SQLQueryAgent.new(llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"]), db: database)
286
285
  ```
287
286
  ```ruby
288
- agent.ask(question: "How many users have a name with length greater than 5 in the users table?")
287
+ agent.run(question: "How many users have a name with length greater than 5 in the users table?")
289
288
  #=> "14 users have a name with length greater than 5 in the users table."
290
289
  ```
291
290
 
@@ -1,10 +1,10 @@
1
1
  require "langchain"
2
2
 
3
3
  # Create a prompt with a few shot examples
4
- prompt = Prompt::FewShotPromptTemplate.new(
4
+ prompt = Langchain::Prompt::FewShotPromptTemplate.new(
5
5
  prefix: "Write antonyms for the following words.",
6
6
  suffix: "Input: {adjective}\nOutput:",
7
- example_prompt: Prompt::PromptTemplate.new(
7
+ example_prompt: Langchain::Prompt::PromptTemplate.new(
8
8
  input_variables: ["input", "output"],
9
9
  template: "Input: {input}\nOutput: {output}"
10
10
  ),
@@ -32,5 +32,5 @@ prompt.format(adjective: "good")
32
32
  prompt.save(file_path: "spec/fixtures/prompt/few_shot_prompt_template.json")
33
33
 
34
34
  # Loading a new prompt template using a JSON file
35
- prompt = Prompt.load_from_path(file_path: "spec/fixtures/prompt/few_shot_prompt_template.json")
35
+ prompt = Langchain::Prompt.load_from_path(file_path: "spec/fixtures/prompt/few_shot_prompt_template.json")
36
36
  prompt.prefix # "Write antonyms for the following words."
@@ -1,15 +1,15 @@
1
1
  require "langchain"
2
2
 
3
3
  # Create a prompt with one input variable
4
- prompt = Prompt::PromptTemplate.new(template: "Tell me a {adjective} joke.", input_variables: ["adjective"])
4
+ prompt = Langchain::Prompt::PromptTemplate.new(template: "Tell me a {adjective} joke.", input_variables: ["adjective"])
5
5
  prompt.format(adjective: "funny") # "Tell me a funny joke."
6
6
 
7
7
  # Create a prompt with multiple input variables
8
- prompt = Prompt::PromptTemplate.new(template: "Tell me a {adjective} joke about {content}.", input_variables: ["adjective", "content"])
8
+ prompt = Langchain::Prompt::PromptTemplate.new(template: "Tell me a {adjective} joke about {content}.", input_variables: ["adjective", "content"])
9
9
  prompt.format(adjective: "funny", content: "chickens") # "Tell me a funny joke about chickens."
10
10
 
11
11
  # Creating a PromptTemplate using just a prompt and no input_variables
12
- prompt = Prompt::PromptTemplate.from_template("Tell me a {adjective} joke about {content}.")
12
+ prompt = Langchain::Prompt::PromptTemplate.from_template("Tell me a {adjective} joke about {content}.")
13
13
  prompt.input_variables # ["adjective", "content"]
14
14
  prompt.format(adjective: "funny", content: "chickens") # "Tell me a funny joke about chickens."
15
15
 
@@ -17,5 +17,9 @@ prompt.format(adjective: "funny", content: "chickens") # "Tell me a funny joke a
17
17
  prompt.save(file_path: "spec/fixtures/prompt/prompt_template.json")
18
18
 
19
19
  # Loading a new prompt template using a JSON file
20
- prompt = Prompt.load_from_path(file_path: "spec/fixtures/prompt/prompt_template.json")
20
+ prompt = Langchain::Prompt.load_from_path(file_path: "spec/fixtures/prompt/prompt_template.json")
21
+ prompt.input_variables # ["adjective", "content"]
22
+
23
+ # Loading a new prompt template using a YAML file
24
+ prompt = Langchain::Prompt.load_from_path(file_path: "spec/fixtures/prompt/prompt_template.yaml")
21
25
  prompt.input_variables # ["adjective", "content"]
@@ -4,7 +4,7 @@ require "langchain"
4
4
  # or add `gem "chroma-db", "~> 0.3.0"` to your Gemfile
5
5
 
6
6
  # Instantiate the Chroma client
7
- chroma = Vectorsearch::Chroma.new(
7
+ chroma = Langchain::Vectorsearch::Chroma.new(
8
8
  url: ENV["CHROMA_URL"],
9
9
  index_name: "documents",
10
10
  llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
@@ -4,7 +4,7 @@ require "langchain"
4
4
  # or add `gem "pinecone"` to your Gemfile
5
5
 
6
6
  # Instantiate the Qdrant client
7
- pinecone = Vectorsearch::Pinecone.new(
7
+ pinecone = Langchain::Vectorsearch::Pinecone.new(
8
8
  environment: ENV["PINECONE_ENVIRONMENT"],
9
9
  api_key: ENV["PINECONE_API_KEY"],
10
10
  index_name: "recipes",
@@ -37,7 +37,7 @@ pinecone.ask(
37
37
  )
38
38
 
39
39
  # Generate your an embedding and search by it
40
- openai = LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
40
+ openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
41
41
  embedding = openai.embed(text: "veggie")
42
42
 
43
43
  pinecone.similarity_search_by_vector(
@@ -4,7 +4,7 @@ require "langchain"
4
4
  # or add `gem "qdrant-ruby"` to your Gemfile
5
5
 
6
6
  # Instantiate the Qdrant client
7
- qdrant = Vectorsearch::Qdrant.new(
7
+ qdrant = Langchain::Vectorsearch::Qdrant.new(
8
8
  url: ENV["QDRANT_URL"],
9
9
  api_key: ENV["QDRANT_API_KEY"],
10
10
  index_name: "recipes",
@@ -4,7 +4,7 @@ require "langchain"
4
4
  # or add `gem "weaviate-ruby"` to your Gemfile
5
5
 
6
6
  # Instantiate the Weaviate client
7
- weaviate = Vectorsearch::Weaviate.new(
7
+ weaviate = Langchain::Vectorsearch::Weaviate.new(
8
8
  url: ENV["WEAVIATE_URL"],
9
9
  api_key: ENV["WEAVIATE_API_KEY"],
10
10
  index_name: "Recipes",
@@ -39,11 +39,8 @@ module Langchain::Agent
39
39
 
40
40
  loop do
41
41
  Langchain.logger.info("[#{self.class.name}]".red + ": Sending the prompt to the #{llm.class} LLM")
42
- response = llm.complete(
43
- prompt: prompt,
44
- stop_sequences: ["Observation:"],
45
- max_tokens: 500
46
- )
42
+
43
+ response = llm.complete(prompt: prompt, stop_sequences: ["Observation:"])
47
44
 
48
45
  # Append the response to the prompt
49
46
  prompt += response
@@ -22,12 +22,12 @@ module Langchain::Agent
22
22
  # @param question [String] Question to ask the LLM/Database
23
23
  # @return [String] Answer to the question
24
24
  #
25
- def ask(question:)
25
+ def run(question:)
26
26
  prompt = create_prompt_for_sql(question: question)
27
27
 
28
28
  # Get the SQL string to execute
29
29
  Langchain.logger.info("[#{self.class.name}]".red + ": Passing the inital prompt to the #{llm.class} LLM")
30
- sql_string = llm.complete(prompt: prompt, max_tokens: 500)
30
+ sql_string = llm.complete(prompt: prompt)
31
31
 
32
32
  # Execute the SQL string and collect the results
33
33
  Langchain.logger.info("[#{self.class.name}]".red + ": Passing the SQL to the Database: #{sql_string}")
@@ -36,7 +36,7 @@ module Langchain::Agent
36
36
  # Pass the results and get the LLM to synthesize the answer to the question
37
37
  Langchain.logger.info("[#{self.class.name}]".red + ": Passing the synthesize prompt to the #{llm.class} LLM with results: #{results}")
38
38
  prompt2 = create_prompt_for_answer(question: question, sql_query: sql_string, results: results)
39
- llm.complete(prompt: prompt2, max_tokens: 500)
39
+ llm.complete(prompt: prompt2)
40
40
  end
41
41
 
42
42
  private
@@ -35,7 +35,7 @@ module Langchain::LLM
35
35
  def embed(text:, **params)
36
36
  parameters = {model: DEFAULTS[:embeddings_model_name], input: text}
37
37
 
38
- Langchain::Utils::TokenLengthValidator.validate!(text, parameters[:model])
38
+ Langchain::Utils::TokenLengthValidator.validate_max_tokens!(text, parameters[:model])
39
39
 
40
40
  response = client.embeddings(parameters: parameters.merge(params))
41
41
  response.dig("data").first.dig("embedding")
@@ -50,9 +50,8 @@ module Langchain::LLM
50
50
  def complete(prompt:, **params)
51
51
  parameters = compose_parameters DEFAULTS[:completion_model_name], params
52
52
 
53
- Langchain::Utils::TokenLengthValidator.validate!(prompt, parameters[:model])
54
-
55
53
  parameters[:prompt] = prompt
54
+ parameters[:max_tokens] = Langchain::Utils::TokenLengthValidator.validate_max_tokens!(prompt, parameters[:model])
56
55
 
57
56
  response = client.completions(parameters: parameters)
58
57
  response.dig("choices", 0, "text")
@@ -67,9 +66,8 @@ module Langchain::LLM
67
66
  def chat(prompt:, **params)
68
67
  parameters = compose_parameters DEFAULTS[:chat_completion_model_name], params
69
68
 
70
- Langchain::Utils::TokenLengthValidator.validate!(prompt, parameters[:model])
71
-
72
69
  parameters[:messages] = [{role: "user", content: prompt}]
70
+ parameters[:max_tokens] = Langchain::Utils::TokenLengthValidator.validate_max_tokens!(prompt, parameters[:model])
73
71
 
74
72
  response = client.chat(parameters: parameters)
75
73
  response.dig("choices", 0, "message", "content")
@@ -87,12 +85,7 @@ module Langchain::LLM
87
85
  )
88
86
  prompt = prompt_template.format(text: text)
89
87
 
90
- complete(
91
- prompt: prompt,
92
- temperature: DEFAULTS[:temperature],
93
- # Most models have a context length of 2048 tokens (except for the newest models, which support 4096).
94
- max_tokens: 2048
95
- )
88
+ complete(prompt: prompt, temperature: DEFAULTS[:temperature])
96
89
  end
97
90
 
98
91
  private
@@ -38,7 +38,8 @@ module Langchain::Tool
38
38
  hash_results = Langchain::Tool::SerpApi
39
39
  .new(api_key: ENV["SERPAPI_API_KEY"])
40
40
  .execute_search(input: input)
41
- hash_results.dig(:answer_box, :to)
41
+ hash_results.dig(:answer_box, :to) ||
42
+ hash_results.dig(:answer_box, :result)
42
43
  end
43
44
  end
44
45
  end
@@ -34,23 +34,50 @@ module Langchain
34
34
  "ada" => 2049
35
35
  }.freeze
36
36
 
37
+ # GOOGLE_PALM_TOKEN_LIMITS = {
38
+ # "chat-bison-001" => {
39
+ # "inputTokenLimit"=>4096,
40
+ # "outputTokenLimit"=>1024
41
+ # },
42
+ # "text-bison-001" => {
43
+ # "inputTokenLimit"=>8196,
44
+ # "outputTokenLimit"=>1024
45
+ # },
46
+ # "embedding-gecko-001" => {
47
+ # "inputTokenLimit"=>1024
48
+ # }
49
+ # }.freeze
50
+
37
51
  #
38
- # Validate the length of the text passed in to OpenAI's API
52
+ # Calculate the `max_tokens:` parameter to be set by calculating the context length of the text minus the prompt length
39
53
  #
40
54
  # @param text [String] The text to validate
41
55
  # @param model_name [String] The model name to validate against
42
- # @return [Boolean] Whether the text is valid or not
56
+ # @return [Integer] Whether the text is valid or not
43
57
  # @raise [TokenLimitExceeded] If the text is too long
44
58
  #
45
- def self.validate!(text, model_name)
46
- encoder = Tiktoken.encoding_for_model(model_name)
47
- token_length = encoder.encode(text).length
59
+ def self.validate_max_tokens!(text, model_name)
60
+ text_token_length = token_length(text, model_name)
61
+ max_tokens = TOKEN_LIMITS[model_name] - text_token_length
48
62
 
49
- if token_length > TOKEN_LIMITS[model_name]
50
- raise TokenLimitExceeded, "This model's maximum context length is #{TOKEN_LIMITS[model_name]} tokens, but the given text is #{token_length} tokens long."
63
+ # Raise an error even if whole prompt is equal to the model's token limit (max_tokens == 0) since not response will be returned
64
+ if max_tokens <= 0
65
+ raise TokenLimitExceeded, "This model's maximum context length is #{TOKEN_LIMITS[model_name]} tokens, but the given text is #{text_token_length} tokens long."
51
66
  end
52
67
 
53
- true
68
+ max_tokens
69
+ end
70
+
71
+ #
72
+ # Calculate token length for a given text and model name
73
+ #
74
+ # @param text [String] The text to validate
75
+ # @param model_name [String] The model name to validate against
76
+ # @return [Integer] The token length of the text
77
+ #
78
+ def self.token_length(text, model_name)
79
+ encoder = Tiktoken.encoding_for_model(model_name)
80
+ encoder.encode(text).length
54
81
  end
55
82
  end
56
83
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain
4
- VERSION = "0.5.1"
4
+ VERSION = "0.5.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: langchainrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Bondarev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-06-06 00:00:00.000000000 Z
11
+ date: 2023-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tiktoken_ruby