langchainrb 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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