langchainrb 0.7.5 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -0
  3. data/README.md +113 -56
  4. data/lib/langchain/assistants/assistant.rb +213 -0
  5. data/lib/langchain/assistants/message.rb +58 -0
  6. data/lib/langchain/assistants/thread.rb +34 -0
  7. data/lib/langchain/chunker/markdown.rb +37 -0
  8. data/lib/langchain/chunker/recursive_text.rb +0 -2
  9. data/lib/langchain/chunker/semantic.rb +1 -3
  10. data/lib/langchain/chunker/sentence.rb +0 -2
  11. data/lib/langchain/chunker/text.rb +0 -2
  12. data/lib/langchain/contextual_logger.rb +1 -1
  13. data/lib/langchain/data.rb +4 -3
  14. data/lib/langchain/llm/ai21.rb +1 -1
  15. data/lib/langchain/llm/anthropic.rb +86 -11
  16. data/lib/langchain/llm/aws_bedrock.rb +52 -0
  17. data/lib/langchain/llm/azure.rb +10 -97
  18. data/lib/langchain/llm/base.rb +3 -2
  19. data/lib/langchain/llm/cohere.rb +5 -7
  20. data/lib/langchain/llm/google_palm.rb +4 -2
  21. data/lib/langchain/llm/google_vertex_ai.rb +151 -0
  22. data/lib/langchain/llm/hugging_face.rb +1 -1
  23. data/lib/langchain/llm/llama_cpp.rb +18 -16
  24. data/lib/langchain/llm/mistral_ai.rb +68 -0
  25. data/lib/langchain/llm/ollama.rb +209 -27
  26. data/lib/langchain/llm/openai.rb +138 -170
  27. data/lib/langchain/llm/prompts/ollama/summarize_template.yaml +9 -0
  28. data/lib/langchain/llm/replicate.rb +1 -7
  29. data/lib/langchain/llm/response/anthropic_response.rb +20 -0
  30. data/lib/langchain/llm/response/base_response.rb +7 -0
  31. data/lib/langchain/llm/response/google_palm_response.rb +4 -0
  32. data/lib/langchain/llm/response/google_vertex_ai_response.rb +33 -0
  33. data/lib/langchain/llm/response/llama_cpp_response.rb +13 -0
  34. data/lib/langchain/llm/response/mistral_ai_response.rb +39 -0
  35. data/lib/langchain/llm/response/ollama_response.rb +27 -1
  36. data/lib/langchain/llm/response/openai_response.rb +8 -0
  37. data/lib/langchain/loader.rb +3 -2
  38. data/lib/langchain/output_parsers/base.rb +0 -4
  39. data/lib/langchain/output_parsers/output_fixing_parser.rb +7 -14
  40. data/lib/langchain/output_parsers/structured_output_parser.rb +0 -10
  41. data/lib/langchain/processors/csv.rb +37 -3
  42. data/lib/langchain/processors/eml.rb +64 -0
  43. data/lib/langchain/processors/markdown.rb +17 -0
  44. data/lib/langchain/processors/pptx.rb +29 -0
  45. data/lib/langchain/prompt/loading.rb +1 -1
  46. data/lib/langchain/tool/base.rb +21 -53
  47. data/lib/langchain/tool/calculator/calculator.json +19 -0
  48. data/lib/langchain/tool/{calculator.rb → calculator/calculator.rb} +8 -16
  49. data/lib/langchain/tool/database/database.json +46 -0
  50. data/lib/langchain/tool/database/database.rb +99 -0
  51. data/lib/langchain/tool/file_system/file_system.json +57 -0
  52. data/lib/langchain/tool/file_system/file_system.rb +32 -0
  53. data/lib/langchain/tool/google_search/google_search.json +19 -0
  54. data/lib/langchain/tool/{google_search.rb → google_search/google_search.rb} +5 -15
  55. data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.json +19 -0
  56. data/lib/langchain/tool/{ruby_code_interpreter.rb → ruby_code_interpreter/ruby_code_interpreter.rb} +8 -4
  57. data/lib/langchain/tool/vectorsearch/vectorsearch.json +24 -0
  58. data/lib/langchain/tool/vectorsearch/vectorsearch.rb +36 -0
  59. data/lib/langchain/tool/weather/weather.json +19 -0
  60. data/lib/langchain/tool/{weather.rb → weather/weather.rb} +3 -15
  61. data/lib/langchain/tool/wikipedia/wikipedia.json +19 -0
  62. data/lib/langchain/tool/{wikipedia.rb → wikipedia/wikipedia.rb} +9 -9
  63. data/lib/langchain/utils/token_length/ai21_validator.rb +6 -2
  64. data/lib/langchain/utils/token_length/base_validator.rb +1 -1
  65. data/lib/langchain/utils/token_length/cohere_validator.rb +6 -2
  66. data/lib/langchain/utils/token_length/google_palm_validator.rb +5 -1
  67. data/lib/langchain/utils/token_length/openai_validator.rb +55 -1
  68. data/lib/langchain/utils/token_length/token_limit_exceeded.rb +1 -1
  69. data/lib/langchain/vectorsearch/base.rb +11 -4
  70. data/lib/langchain/vectorsearch/chroma.rb +10 -1
  71. data/lib/langchain/vectorsearch/elasticsearch.rb +53 -4
  72. data/lib/langchain/vectorsearch/epsilla.rb +149 -0
  73. data/lib/langchain/vectorsearch/hnswlib.rb +5 -1
  74. data/lib/langchain/vectorsearch/milvus.rb +4 -2
  75. data/lib/langchain/vectorsearch/pgvector.rb +14 -4
  76. data/lib/langchain/vectorsearch/pinecone.rb +8 -5
  77. data/lib/langchain/vectorsearch/qdrant.rb +16 -4
  78. data/lib/langchain/vectorsearch/weaviate.rb +20 -2
  79. data/lib/langchain/version.rb +1 -1
  80. data/lib/langchain.rb +20 -5
  81. metadata +182 -45
  82. data/lib/langchain/agent/agents.md +0 -54
  83. data/lib/langchain/agent/base.rb +0 -20
  84. data/lib/langchain/agent/react_agent/react_agent_prompt.yaml +0 -26
  85. data/lib/langchain/agent/react_agent.rb +0 -131
  86. data/lib/langchain/agent/sql_query_agent/sql_query_agent_answer_prompt.yaml +0 -11
  87. data/lib/langchain/agent/sql_query_agent/sql_query_agent_sql_prompt.yaml +0 -21
  88. data/lib/langchain/agent/sql_query_agent.rb +0 -82
  89. data/lib/langchain/conversation/context.rb +0 -8
  90. data/lib/langchain/conversation/memory.rb +0 -86
  91. data/lib/langchain/conversation/message.rb +0 -48
  92. data/lib/langchain/conversation/prompt.rb +0 -8
  93. data/lib/langchain/conversation/response.rb +0 -8
  94. data/lib/langchain/conversation.rb +0 -93
  95. data/lib/langchain/tool/database.rb +0 -90
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4c388275b83a0e4260f4ae9271f4c164a8d34ea5ea9585916d91e7e9c17c980
4
- data.tar.gz: 8daa400de3ed80bb3fb9c53cc19ef4d56f137c2aa157bd268dbda488d0fca432
3
+ metadata.gz: 7f29aad35bc35dc95eb8673b11578b51c7449a19818989d9da5e640c6fb219c7
4
+ data.tar.gz: 4d0c4d3d424a82c7f02fb9e49ca52a5bdca5dfbce19fbfa22f2d74ef46d81eb7
5
5
  SHA512:
6
- metadata.gz: 4bae87c050be6a8fa011c1ae5de4b119abac498669f2e63ca1829e11b7b5ecca7610330be670d24fd6cb98c2e2599c593e9922378985efc586d76c124efb865e
7
- data.tar.gz: 2a39b084c6a239aeb0de22bfc87629d2f2909b23eabfcf71a835a1f1624d84afe3ea106afdafb8f1fb301b7934d73abc7253c9b8bd3f6c9b170231ebb5af0936
6
+ metadata.gz: 91b6f4fc5056308eab9119dcfda1be16857e6e9e6e531977148b1e8f31b72090794b67e6855afb95633b8f836b8d20921bc5a069afdc745d1114892143a177e1
7
+ data.tar.gz: f7a7949ab2efd960eacf3a93f7beaa9104403a93619b8c95ea094901c2d3d19b89980c81d293ae16035c5ff51fe021a09f2e81e2c0ed6854bff87d30e6def925
data/CHANGELOG.md CHANGED
@@ -1,5 +1,83 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.12.0] - 2024-04-22
4
+ - [BREAKING] Rename `dimension` parameter to `dimensions` everywhere
5
+
6
+ ## [0.11.4] - 2024-04-19
7
+ - New `Langchain::LLM::AWSBedrock#chat()` to wrap Bedrock Claude requests
8
+ - New `Langchain::LLM::OllamaResponse#total_tokens()` method
9
+
10
+ ## [0.11.3] - 2024-04-16
11
+ - New `Langchain::Processors::Pptx` to parse .pptx files
12
+ - New `Langchain::LLM::Anthropic#chat()` support
13
+ - Misc fixes
14
+
15
+ ## [0.11.2]
16
+ - New `Langchain::Assistant#clear_thread!` and `Langchain::Assistant#instructions=` methods
17
+
18
+ ## [0.11.1]
19
+ - Langchain::Tool::Vectorsearch that wraps Langchain::Vectorsearch::* classes. This allows the Assistant to call the tool and inject data from vector DBs.
20
+
21
+ ## [0.11.0]
22
+ - Delete previously deprecated `Langchain::Agent::ReActAgent` and `Langchain::Agent::SQLQueryAgent` classes
23
+ - New `Langchain::Agent::FileSystem` tool that can read files, write to files, and list the contents of a directory
24
+
25
+ ## [0.10.3]
26
+ - Bump dependencies
27
+ - Ollama#complete fix
28
+ - Misc fixes
29
+
30
+ ## [0.10.2]
31
+ - New Langchain::LLM::Mistral
32
+ - Drop Ruby 3.0 support
33
+ - Fixes Zeitwerk::NameError
34
+
35
+ ## [0.10.1] - GEM VERSION YANKED
36
+
37
+ ## [0.10.0]
38
+ - Delete `Langchain::Conversation` class
39
+
40
+ ## [0.9.5]
41
+ - Now using OpenAI's "text-embedding-3-small" model to generate embeddings
42
+ - Added `remove_texts(ids:)` method to Qdrant and Chroma
43
+ - Add Ruby 3.3 support
44
+
45
+ ## [0.9.4]
46
+ - New `Ollama#summarize()` method
47
+ - Improved README
48
+ - Fixes + specs
49
+
50
+ ## [0.9.3]
51
+ - Add EML processor
52
+ - Tools can support multiple-methods
53
+ - Bump gems and bug fixes
54
+
55
+ ## [0.9.2]
56
+ - Fix vectorsearch#ask methods
57
+ - Bump cohere-ruby gem
58
+
59
+ ## [0.9.1]
60
+ - Add support for new OpenAI models
61
+ - Add Ollama#chat method
62
+ - Fix and refactor of `Langchain::LLM::Ollama`, responses can now be streamed.
63
+
64
+ ## [0.9.0]
65
+ - Introducing new `Langchain::Assistant` that will be replacing `Langchain::Conversation` and `Langchain::Agent`s.
66
+ - `Langchain::Conversation` is deprecated.
67
+
68
+ ## [0.8.2]
69
+ - Introducing new `Langchain::Chunker::Markdown` chunker (thanks @spikex)
70
+ - Fixes
71
+
72
+ ## [0.8.1]
73
+ - Support for Epsilla vector DB
74
+ - Fully functioning Google Vertex AI LLM
75
+ - Bug fixes
76
+
77
+ ## [0.8.0]
78
+ - [BREAKING] Updated llama_cpp.rb to 0.9.4. The model file format used by the underlying llama.cpp library has changed to GGUF. llama.cpp ships with scripts to convert existing files and GGUF format models can be downloaded from HuggingFace.
79
+ - Introducing Langchain::LLM::GoogleVertexAi LLM provider
80
+
3
81
  ## [0.7.5] - 2023-11-13
4
82
  - Fixes
5
83
 
data/README.md CHANGED
@@ -15,8 +15,7 @@ Available for paid consulting engagements! [Email me](mailto:andrei@sourcelabs.i
15
15
 
16
16
  ## Use Cases
17
17
  * Retrieval Augmented Generation (RAG) and vector search
18
- * Chat bots
19
- * [AI agents](https://github.com/andreibondarev/langchainrb/tree/main/lib/langchain/agent/agents.md)
18
+ * [Assistants](#assistants) (chat bots)
20
19
 
21
20
  ## Table of Contents
22
21
 
@@ -26,10 +25,11 @@ Available for paid consulting engagements! [Email me](mailto:andrei@sourcelabs.i
26
25
  - [Prompt Management](#prompt-management)
27
26
  - [Output Parsers](#output-parsers)
28
27
  - [Building RAG](#building-retrieval-augment-generation-rag-system)
29
- - [Building chat bots](#building-chat-bots)
28
+ - [Assistants](#assistants)
30
29
  - [Evaluations](#evaluations-evals)
31
30
  - [Examples](#examples)
32
31
  - [Logging](#logging)
32
+ - [Problems](#problems)
33
33
  - [Development](#development)
34
34
  - [Discord](#discord)
35
35
 
@@ -43,6 +43,8 @@ If bundler is not being used to manage dependencies, install the gem by executin
43
43
 
44
44
  gem install langchainrb
45
45
 
46
+ Additional gems may be required. They're not included by default so you can include only what you need.
47
+
46
48
  ## Usage
47
49
 
48
50
  ```ruby
@@ -50,26 +52,30 @@ require "langchain"
50
52
  ```
51
53
 
52
54
  ## Large Language Models (LLMs)
53
- Langchain.rb wraps all supported LLMs in a unified interface allowing you to easily swap out and test out different models.
55
+ Langchain.rb wraps supported LLMs in a unified interface allowing you to easily swap out and test out different models.
54
56
 
55
57
  #### Supported LLMs and features:
56
- | LLM providers | embed() | complete() | chat() | summarize() | Notes |
57
- | -------- |:------------------:| :-------: | :-----------------: | :-------: | :----------------- |
58
- | [OpenAI](https://openai.com/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | ❌ | Including Azure OpenAI |
59
- | [AI21](https://ai21.com/) | ❌ | :white_check_mark: | ❌ | :white_check_mark: | |
60
- | [Anthropic](https://milvus.io/) | ❌ | :white_check_mark: | | ❌ | |
61
- | [AWS Bedrock](https://aws.amazon.com/bedrock) | :white_check_mark: | :white_check_mark: | | ❌ | Provides AWS, Cohere, AI21, Antropic and Stability AI models |
62
- | [Cohere](https://www.pinecone.io/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
63
- | [GooglePalm](https://ai.google/discover/palm2/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
64
- | [HuggingFace](https://huggingface.co/) | :white_check_mark: | | ❌ | | |
65
- | [Ollama](https://ollama.ai/) | :white_check_mark: | :white_check_mark: | ❌ | ❌ | |
66
- | [Replicate](https://replicate.com/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
58
+ | LLM providers | `embed()` | `complete()` | `chat()` | `summarize()` | Notes |
59
+ | -------- |:------------------:| :-------: | :-----------------: | :-------: | :----------------- |
60
+ | [OpenAI](https://openai.com/?utm_source=langchainrb&utm_medium=github) | | | | ❌ | Including Azure OpenAI |
61
+ | [AI21](https://ai21.com/?utm_source=langchainrb&utm_medium=github) | ❌ | | ❌ | | |
62
+ | [Anthropic](https://anthropic.com/?utm_source=langchainrb&utm_medium=github) | ❌ | | | ❌ | |
63
+ | [AWS Bedrock](https://aws.amazon.com/bedrock?utm_source=langchainrb&utm_medium=github) | | | | ❌ | Provides AWS, Cohere, AI21, Antropic and Stability AI models |
64
+ | [Cohere](https://cohere.com/?utm_source=langchainrb&utm_medium=github) | | | | | |
65
+ | [GooglePalm](https://ai.google/discover/palm2?utm_source=langchainrb&utm_medium=github) | | | | | |
66
+ | [Google Vertex AI](https://cloud.google.com/vertex-ai?utm_source=langchainrb&utm_medium=github) | | | ❌ | | |
67
+ | [HuggingFace](https://huggingface.co/?utm_source=langchainrb&utm_medium=github) | | | ❌ | ❌ | |
68
+ | [Mistral AI](https://mistral.ai/?utm_source=langchainrb&utm_medium=github) | | | | | |
69
+ | [Ollama](https://ollama.ai/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ✅ | ✅ | |
70
+ | [Replicate](https://replicate.com/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ✅ | ✅ | |
71
+
72
+
67
73
 
68
74
  #### Using standalone LLMs:
69
75
 
70
76
  #### OpenAI
71
77
 
72
- Add `gem "ruby-openai", "~> 5.2.0"` to your Gemfile.
78
+ Add `gem "ruby-openai", "~> 6.3.0"` to your Gemfile.
73
79
 
74
80
  ```ruby
75
81
  llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
@@ -81,27 +87,22 @@ llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"], llm_options: {
81
87
 
82
88
  Generate vector embeddings:
83
89
  ```ruby
84
- llm.embed(text: "foo bar")
85
- ```
86
-
87
- Generate a text completion:
88
- ```ruby
89
- llm.complete(prompt: "What is the meaning of life?")
90
+ llm.embed(text: "foo bar").embedding
90
91
  ```
91
92
 
92
93
  Generate a chat completion:
93
94
  ```ruby
94
- llm.chat(prompt: "Hey! How are you?")
95
+ llm.chat(messages: [{role: "user", content: "What is the meaning of life?"}]).completion
95
96
  ```
96
97
 
97
98
  Summarize the text:
98
99
  ```ruby
99
- llm.complete(text: "...")
100
+ llm.summarize(text: "...").completion
100
101
  ```
101
102
 
102
103
  You can use any other LLM by invoking the same interface:
103
104
  ```ruby
104
- llm = Langchain::LLM::GooglePalm.new(...)
105
+ llm = Langchain::LLM::GooglePalm.new(api_key: ENV["GOOGLE_PALM_API_KEY"], default_options: { ... })
105
106
  ```
106
107
 
107
108
  ### Prompt Management
@@ -247,7 +248,7 @@ Then parse the llm response:
247
248
 
248
249
  ```ruby
249
250
  llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
250
- llm_response = llm.chat(prompt: prompt_text)
251
+ llm_response = llm.chat(messages: [{role: "user", content: prompt_text}]).completion
251
252
  parser.parse(llm_response)
252
253
  # {
253
254
  # "name" => "Kim Ji-hyun",
@@ -303,15 +304,17 @@ Langchain.rb provides a convenient unified interface on top of supported vectors
303
304
 
304
305
  #### Supported vector search databases and features:
305
306
 
306
- | Database | Open-source | Cloud offering |
307
- | -------- |:------------------:| :------------: |
308
- | [Chroma](https://trychroma.com/) | :white_check_mark: | :white_check_mark: |
309
- | [Hnswlib](https://github.com/nmslib/hnswlib/) | :white_check_mark: | |
310
- | [Milvus](https://milvus.io/) | :white_check_mark: | :white_check_mark: Zilliz Cloud |
311
- | [Pinecone](https://www.pinecone.io/) | | :white_check_mark: |
312
- | [Pgvector](https://github.com/pgvector/pgvector) | :white_check_mark: | :white_check_mark: |
313
- | [Qdrant](https://qdrant.tech/) | :white_check_mark: | :white_check_mark: |
314
- | [Weaviate](https://weaviate.io/) | :white_check_mark: | :white_check_mark: |
307
+ | Database | Open-source | Cloud offering |
308
+ | -------- |:------------------:| :------------: |
309
+ | [Chroma](https://trychroma.com/?utm_source=langchainrb&utm_medium=github) | | |
310
+ | [Epsilla](https://epsilla.com/?utm_source=langchainrb&utm_medium=github) | | |
311
+ | [Hnswlib](https://github.com/nmslib/hnswlib/?utm_source=langchainrb&utm_medium=github) | | |
312
+ | [Milvus](https://milvus.io/?utm_source=langchainrb&utm_medium=github) | | Zilliz Cloud |
313
+ | [Pinecone](https://www.pinecone.io/?utm_source=langchainrb&utm_medium=github) | | |
314
+ | [Pgvector](https://github.com/pgvector/pgvector/?utm_source=langchainrb&utm_medium=github) | | |
315
+ | [Qdrant](https://qdrant.tech/?utm_source=langchainrb&utm_medium=github) | | |
316
+ | [Weaviate](https://weaviate.io/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ |
317
+ | [Elasticsearch](https://www.elastic.co/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ |
315
318
 
316
319
  ### Using Vector Search Databases 🔍
317
320
 
@@ -337,11 +340,13 @@ client = Langchain::Vectorsearch::Weaviate.new(
337
340
  You can instantiate any other supported vector search database:
338
341
  ```ruby
339
342
  client = Langchain::Vectorsearch::Chroma.new(...) # `gem "chroma-db", "~> 0.6.0"`
343
+ client = Langchain::Vectorsearch::Epsilla.new(...) # `gem "epsilla-ruby", "~> 0.0.3"`
340
344
  client = Langchain::Vectorsearch::Hnswlib.new(...) # `gem "hnswlib", "~> 0.8.1"`
341
345
  client = Langchain::Vectorsearch::Milvus.new(...) # `gem "milvus", "~> 0.9.2"`
342
346
  client = Langchain::Vectorsearch::Pinecone.new(...) # `gem "pinecone", "~> 0.1.6"`
343
347
  client = Langchain::Vectorsearch::Pgvector.new(...) # `gem "pgvector", "~> 0.2"`
344
- client = Langchain::Vectorsearch::Qdrant.new(...) # `gem"qdrant-ruby", "~> 0.9.3"`
348
+ client = Langchain::Vectorsearch::Qdrant.new(...) # `gem "qdrant-ruby", "~> 0.9.3"`
349
+ client = Langchain::Vectorsearch::Elasticsearch.new(...) # `gem "elasticsearch", "~> 8.2.0"`
345
350
  ```
346
351
 
347
352
  Create the default schema:
@@ -367,7 +372,7 @@ my_docx = Langchain.root.join("path/to/my.docx")
367
372
 
368
373
  client.add_data(paths: [my_pdf, my_text, my_docx])
369
374
  ```
370
- Supported file formats: docx, html, pdf, text, json, jsonl, csv, xlsx.
375
+ Supported file formats: docx, html, pdf, text, json, jsonl, csv, xlsx, eml, pptx.
371
376
 
372
377
  Retrieve similar documents based on the query string passed in:
373
378
  ```ruby
@@ -392,46 +397,92 @@ client.similarity_search_by_vector(
392
397
 
393
398
  RAG-based querying
394
399
  ```ruby
395
- client.ask(
396
- question:
397
- )
400
+ client.ask(question: "...")
398
401
  ```
399
402
 
400
- ## Building chat bots
403
+ ## Assistants
404
+ Assistants are Agent-like objects that leverage helpful instructions, LLMs, tools and knowledge to respond to user queries. Assistants can be configured with an LLM of your choice (currently only OpenAI), any vector search database and easily extended with additional tools.
401
405
 
402
- ### Conversation class
406
+ ### Available Tools 🛠️
403
407
 
404
- Choose and instantiate the LLM provider you'll be using:
408
+ | Name | Description | ENV Requirements | Gem Requirements |
409
+ | ------------ | :------------------------------------------------: | :-----------------------------------------------------------: | :---------------------------------------: |
410
+ | "calculator" | Useful for getting the result of a math expression | | `gem "eqn", "~> 1.6.5"` |
411
+ | "database" | Useful for querying a SQL database | | `gem "sequel", "~> 5.68.0"` |
412
+ | "file_system" | Interacts with the file system | | |
413
+ | "ruby_code_interpreter" | Interprets Ruby expressions | | `gem "safe_ruby", "~> 1.0.4"` |
414
+ | "google_search" | A wrapper around Google Search | `ENV["SERPAPI_API_KEY"]` (https://serpapi.com/manage-api-key) | `gem "google_search_results", "~> 2.0.0"` |
415
+ | "weather" | Calls Open Weather API to retrieve the current weather | `ENV["OPEN_WEATHER_API_KEY"]` (https://home.openweathermap.org/api_keys) | `gem "open-weather-ruby-client", "~> 0.3.0"` |
416
+ | "wikipedia" | Calls Wikipedia API to retrieve the summary | | `gem "wikipedia-client", "~> 1.17.0"` |
417
+
418
+ ### Demos
419
+ 1. [Building an AI Assistant that operates a simulated E-commerce Store](https://www.loom.com/share/83aa4fd8dccb492aad4ca95da40ed0b2)
420
+ 2. [New Langchain.rb Assistants interface](https://www.loom.com/share/e883a4a49b8746c1b0acf9d58cf6da36)
421
+
422
+ ### Creating an Assistant
423
+ 1. Instantiate an LLM of your choice
405
424
  ```ruby
406
425
  llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
407
426
  ```
408
- Instantiate the Conversation class:
427
+ 2. Instantiate a Thread. Threads keep track of the messages in the Assistant conversation.
428
+ ```ruby
429
+ thread = Langchain::Thread.new
430
+ ```
431
+ You can pass old message from previously using the Assistant:
409
432
  ```ruby
410
- chat = Langchain::Conversation.new(llm: llm)
433
+ thread.messages = messages
411
434
  ```
435
+ Messages contain the conversation history and the whole message history is sent to the LLM every time. A Message belongs to 1 of the 4 roles:
436
+ * `Message(role: "system")` message usually contains the instructions.
437
+ * `Message(role: "user")` messages come from the user.
438
+ * `Message(role: "assistant")` messages are produced by the LLM.
439
+ * `Message(role: "tool")` messages are sent in response to tool calls with tool outputs.
412
440
 
413
- (Optional) Set the conversation context:
441
+ 3. Instantiate an Assistant
414
442
  ```ruby
415
- chat.set_context("You are a chatbot from the future")
443
+ assistant = Langchain::Assistant.new(
444
+ llm: llm,
445
+ thread: thread,
446
+ instructions: "You are a Meteorologist Assistant that is able to pull the weather for any location",
447
+ tools: [
448
+ Langchain::Tool::GoogleSearch.new(api_key: ENV["SERPAPI_API_KEY"])
449
+ ]
450
+ )
451
+ ```
452
+ ### Using an Assistant
453
+ You can now add your message to an Assistant.
454
+ ```ruby
455
+ assistant.add_message content: "What's the weather in New York City?"
416
456
  ```
417
457
 
418
- Exchange messages with the LLM
458
+ Run the Assistant to generate a response.
419
459
  ```ruby
420
- chat.message("Tell me about future technologies")
460
+ assistant.run
421
461
  ```
422
462
 
423
- To stream the chat response:
463
+ If a Tool is invoked you can manually submit an output.
424
464
  ```ruby
425
- chat = Langchain::Conversation.new(llm: llm) do |chunk|
426
- print(chunk)
427
- end
465
+ assistant.submit_tool_output tool_call_id: "...", output: "It's 70 degrees and sunny in New York City"
466
+ ```
467
+
468
+ Or run the assistant with `auto_tool_execution: tool` to call Tools automatically.
469
+ ```ruby
470
+ assistant.add_message content: "How about San Diego, CA?"
471
+ assistant.run(auto_tool_execution: true)
472
+ ```
473
+ You can also combine the two by calling:
474
+ ```ruby
475
+ assistant.add_message_and_run content: "What about Sacramento, CA?", auto_tool_execution: true
428
476
  ```
429
477
 
430
- Open AI Functions support
478
+ ### Accessing Thread messages
479
+ You can access the messages in a Thread by calling `assistant.thread.messages`.
431
480
  ```ruby
432
- chat.set_functions(functions)
481
+ assistant.thread.messages
433
482
  ```
434
483
 
484
+ The Assistant checks the context window limits before every request to the LLM and remove oldest thread messages one by one if the context window is exceeded.
485
+
435
486
  ## Evaluations (Evals)
436
487
  The Evaluations module is a collection of tools that can be used to evaluate and track the performance of the output products by LLM and your RAG (Retrieval Augmented Generation) pipelines.
437
488
 
@@ -463,13 +514,19 @@ Additional examples available: [/examples](https://github.com/andreibondarev/lan
463
514
 
464
515
  ## Logging
465
516
 
466
- LangChain.rb uses standard logging mechanisms and defaults to `:warn` level. Most messages are at info level, but we will add debug or warn statements as needed.
517
+ Langchain.rb uses standard logging mechanisms and defaults to `:warn` level. Most messages are at info level, but we will add debug or warn statements as needed.
467
518
  To show all log messages:
468
519
 
469
520
  ```ruby
470
521
  Langchain.logger.level = :debug
471
522
  ```
472
523
 
524
+ ## Problems
525
+ If you're having issues installing `unicode` gem required by `pragmatic_segmenter`, try running:
526
+ ```bash
527
+ gem install unicode -- --with-cflags="-Wno-incompatible-function-pointer-types"
528
+ ```
529
+
473
530
  ## Development
474
531
 
475
532
  1. `git clone https://github.com/andreibondarev/langchainrb.git`
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langchain
4
+ # Assistants are Agent-like objects that leverage helpful instructions, LLMs, tools and knowledge to respond to user queries.
5
+ # Assistants can be configured with an LLM of your choice (currently only OpenAI), any vector search database and easily extended with additional tools.
6
+ class Assistant
7
+ attr_reader :llm, :thread, :instructions
8
+ attr_accessor :tools
9
+
10
+ # Create a new assistant
11
+ #
12
+ # @param llm [Langchain::LLM::Base] LLM instance that the assistant will use
13
+ # @param thread [Langchain::Thread] The thread that'll keep track of the conversation
14
+ # @param tools [Array<Langchain::Tool::Base>] Tools that the assistant has access to
15
+ # @param instructions [String] The system instructions to include in the thread
16
+ def initialize(
17
+ llm:,
18
+ thread:,
19
+ tools: [],
20
+ instructions: nil
21
+ )
22
+ raise ArgumentError, "Invalid LLM; currently only Langchain::LLM::OpenAI is supported" unless llm.instance_of?(Langchain::LLM::OpenAI)
23
+ raise ArgumentError, "Thread must be an instance of Langchain::Thread" unless thread.is_a?(Langchain::Thread)
24
+ raise ArgumentError, "Tools must be an array of Langchain::Tool::Base instance(s)" unless tools.is_a?(Array) && tools.all? { |tool| tool.is_a?(Langchain::Tool::Base) }
25
+
26
+ @llm = llm
27
+ @thread = thread
28
+ @tools = tools
29
+ @instructions = instructions
30
+
31
+ # The first message in the thread should be the system instructions
32
+ # TODO: What if the user added old messages and the system instructions are already in there? Should this overwrite the existing instructions?
33
+ add_message(role: "system", content: instructions) if instructions
34
+ end
35
+
36
+ # Add a user message to the thread
37
+ #
38
+ # @param content [String] The content of the message
39
+ # @param role [String] The role attribute of the message. Default: "user"
40
+ # @param tool_calls [Array<Hash>] The tool calls to include in the message
41
+ # @param tool_call_id [String] The ID of the tool call to include in the message
42
+ # @return [Array<Langchain::Message>] The messages in the thread
43
+ def add_message(content: nil, role: "user", tool_calls: [], tool_call_id: nil)
44
+ message = build_message(role: role, content: content, tool_calls: tool_calls, tool_call_id: tool_call_id)
45
+ thread.add_message(message)
46
+ end
47
+
48
+ # Run the assistant
49
+ #
50
+ # @param auto_tool_execution [Boolean] Whether or not to automatically run tools
51
+ # @return [Array<Langchain::Message>] The messages in the thread
52
+ def run(auto_tool_execution: false)
53
+ if thread.messages.empty?
54
+ Langchain.logger.warn("No messages in the thread")
55
+ return
56
+ end
57
+
58
+ running = true
59
+
60
+ while running
61
+ # TODO: I think we need to look at all messages and not just the last one.
62
+ case (last_message = thread.messages.last).role
63
+ when "system"
64
+ # Do nothing
65
+ running = false
66
+ when "assistant"
67
+ if last_message.tool_calls.any?
68
+ if auto_tool_execution
69
+ run_tools(last_message.tool_calls)
70
+ else
71
+ # Maybe log and tell the user that there's outstanding tool calls?
72
+ running = false
73
+ end
74
+ else
75
+ # Last message was from the assistant without any tools calls.
76
+ # Do nothing
77
+ running = false
78
+ end
79
+ when "user"
80
+ # Run it!
81
+ response = chat_with_llm
82
+
83
+ if response.tool_calls
84
+ # Re-run the while(running) loop to process the tool calls
85
+ running = true
86
+ add_message(role: response.role, tool_calls: response.tool_calls)
87
+ elsif response.chat_completion
88
+ # Stop the while(running) loop and add the assistant's response to the thread
89
+ running = false
90
+ add_message(role: response.role, content: response.chat_completion)
91
+ end
92
+ when "tool"
93
+ # Run it!
94
+ response = chat_with_llm
95
+ running = true
96
+
97
+ if response.tool_calls
98
+ add_message(role: response.role, tool_calls: response.tool_calls)
99
+ elsif response.chat_completion
100
+ add_message(role: response.role, content: response.chat_completion)
101
+ end
102
+ end
103
+ end
104
+
105
+ thread.messages
106
+ end
107
+
108
+ # Add a user message to the thread and run the assistant
109
+ #
110
+ # @param content [String] The content of the message
111
+ # @param auto_tool_execution [Boolean] Whether or not to automatically run tools
112
+ # @return [Array<Langchain::Message>] The messages in the thread
113
+ def add_message_and_run(content:, auto_tool_execution: false)
114
+ add_message(content: content, role: "user")
115
+ run(auto_tool_execution: auto_tool_execution)
116
+ end
117
+
118
+ # Submit tool output to the thread
119
+ #
120
+ # @param tool_call_id [String] The ID of the tool call to submit output for
121
+ # @param output [String] The output of the tool
122
+ # @return [Array<Langchain::Message>] The messages in the thread
123
+ def submit_tool_output(tool_call_id:, output:)
124
+ # TODO: Validate that `tool_call_id` is valid
125
+ add_message(role: "tool", content: output, tool_call_id: tool_call_id)
126
+ end
127
+
128
+ # Delete all messages in the thread
129
+ #
130
+ # @return [Array] Empty messages array
131
+ def clear_thread!
132
+ # TODO: If this a bug? Should we keep the "system" message?
133
+ thread.messages = []
134
+ end
135
+
136
+ # Set new instructions
137
+ #
138
+ # @param [String] New instructions that will be set as a system message
139
+ # @return [Array<Langchain::Message>] The messages in the thread
140
+ def instructions=(new_instructions)
141
+ @instructions = new_instructions
142
+
143
+ # Find message with role: "system" in thread.messages and delete it from the thread.messages array
144
+ thread.messages.delete_if(&:system?)
145
+
146
+ # Set new instructions by adding new system message
147
+ message = build_message(role: "system", content: new_instructions)
148
+ thread.messages.unshift(message)
149
+ end
150
+
151
+ private
152
+
153
+ # Call to the LLM#chat() method
154
+ #
155
+ # @return [Langchain::LLM::BaseResponse] The LLM response object
156
+ def chat_with_llm
157
+ Langchain.logger.info("Sending a call to #{llm.class}", for: self.class)
158
+
159
+ params = {messages: thread.openai_messages}
160
+
161
+ if tools.any?
162
+ params[:tools] = tools.map(&:to_openai_tools).flatten
163
+ # TODO: Not sure that tool_choice should always be "auto"; Maybe we can let the user toggle it.
164
+ params[:tool_choice] = "auto"
165
+ end
166
+
167
+ llm.chat(**params)
168
+ end
169
+
170
+ # Run the tools automatically
171
+ #
172
+ # @param tool_calls [Array<Hash>] The tool calls to run
173
+ def run_tools(tool_calls)
174
+ # Iterate over each function invocation and submit tool output
175
+ tool_calls.each do |tool_call|
176
+ tool_call_id = tool_call.dig("id")
177
+
178
+ function_name = tool_call.dig("function", "name")
179
+ tool_name, method_name = function_name.split("-")
180
+ tool_arguments = JSON.parse(tool_call.dig("function", "arguments"), symbolize_names: true)
181
+
182
+ tool_instance = tools.find do |t|
183
+ t.name == tool_name
184
+ end or raise ArgumentError, "Tool not found in assistant.tools"
185
+
186
+ output = tool_instance.send(method_name, **tool_arguments)
187
+
188
+ submit_tool_output(tool_call_id: tool_call_id, output: output)
189
+ end
190
+
191
+ response = chat_with_llm
192
+
193
+ if response.tool_calls
194
+ add_message(role: response.role, tool_calls: response.tool_calls)
195
+ elsif response.chat_completion
196
+ add_message(role: response.role, content: response.chat_completion)
197
+ end
198
+ end
199
+
200
+ # Build a message
201
+ #
202
+ # @param role [String] The role of the message
203
+ # @param content [String] The content of the message
204
+ # @param tool_calls [Array<Hash>] The tool calls to include in the message
205
+ # @param tool_call_id [String] The ID of the tool call to include in the message
206
+ # @return [Langchain::Message] The Message object
207
+ def build_message(role:, content: nil, tool_calls: [], tool_call_id: nil)
208
+ Message.new(role: role, content: content, tool_calls: tool_calls, tool_call_id: tool_call_id)
209
+ end
210
+
211
+ # TODO: Fix the message truncation when context window is exceeded
212
+ end
213
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langchain
4
+ # Langchain::Message are the messages that are sent to LLM chat methods
5
+ class Message
6
+ attr_reader :role, :content, :tool_calls, :tool_call_id
7
+
8
+ ROLES = %w[
9
+ system
10
+ assistant
11
+ user
12
+ tool
13
+ ].freeze
14
+
15
+ # @param role [String] The role of the message
16
+ # @param content [String] The content of the message
17
+ # @param tool_calls [Array<Hash>] Tool calls to be made
18
+ # @param tool_call_id [String] The ID of the tool call to be made
19
+ def initialize(role:, content: nil, tool_calls: [], tool_call_id: nil) # TODO: Implement image_file: reference (https://platform.openai.com/docs/api-reference/messages/object#messages/object-content)
20
+ raise ArgumentError, "Role must be one of #{ROLES.join(", ")}" unless ROLES.include?(role)
21
+ raise ArgumentError, "Tool calls must be an array of hashes" unless tool_calls.is_a?(Array) && tool_calls.all? { |tool_call| tool_call.is_a?(Hash) }
22
+
23
+ @role = role
24
+ # Some Tools return content as a JSON hence `.to_s`
25
+ @content = content.to_s
26
+ @tool_calls = tool_calls
27
+ @tool_call_id = tool_call_id
28
+ end
29
+
30
+ # Convert the message to an OpenAI API-compatible hash
31
+ #
32
+ # @return [Hash] The message as an OpenAI API-compatible hash
33
+ def to_openai_format
34
+ {}.tap do |h|
35
+ h[:role] = role
36
+ h[:content] = content if content # Content is nil for tool calls
37
+ h[:tool_calls] = tool_calls if tool_calls.any?
38
+ h[:tool_call_id] = tool_call_id if tool_call_id
39
+ end
40
+ end
41
+
42
+ def assistant?
43
+ role == "assistant"
44
+ end
45
+
46
+ def system?
47
+ role == "system"
48
+ end
49
+
50
+ def user?
51
+ role == "user"
52
+ end
53
+
54
+ def tool?
55
+ role == "tool"
56
+ end
57
+ end
58
+ end