langchainrb 0.7.5 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -0
- data/README.md +113 -56
- data/lib/langchain/assistants/assistant.rb +213 -0
- data/lib/langchain/assistants/message.rb +58 -0
- data/lib/langchain/assistants/thread.rb +34 -0
- data/lib/langchain/chunker/markdown.rb +37 -0
- data/lib/langchain/chunker/recursive_text.rb +0 -2
- data/lib/langchain/chunker/semantic.rb +1 -3
- data/lib/langchain/chunker/sentence.rb +0 -2
- data/lib/langchain/chunker/text.rb +0 -2
- data/lib/langchain/contextual_logger.rb +1 -1
- data/lib/langchain/data.rb +4 -3
- data/lib/langchain/llm/ai21.rb +1 -1
- data/lib/langchain/llm/anthropic.rb +86 -11
- data/lib/langchain/llm/aws_bedrock.rb +52 -0
- data/lib/langchain/llm/azure.rb +10 -97
- data/lib/langchain/llm/base.rb +3 -2
- data/lib/langchain/llm/cohere.rb +5 -7
- data/lib/langchain/llm/google_palm.rb +4 -2
- data/lib/langchain/llm/google_vertex_ai.rb +151 -0
- data/lib/langchain/llm/hugging_face.rb +1 -1
- data/lib/langchain/llm/llama_cpp.rb +18 -16
- data/lib/langchain/llm/mistral_ai.rb +68 -0
- data/lib/langchain/llm/ollama.rb +209 -27
- data/lib/langchain/llm/openai.rb +138 -170
- data/lib/langchain/llm/prompts/ollama/summarize_template.yaml +9 -0
- data/lib/langchain/llm/replicate.rb +1 -7
- data/lib/langchain/llm/response/anthropic_response.rb +20 -0
- data/lib/langchain/llm/response/base_response.rb +7 -0
- data/lib/langchain/llm/response/google_palm_response.rb +4 -0
- data/lib/langchain/llm/response/google_vertex_ai_response.rb +33 -0
- data/lib/langchain/llm/response/llama_cpp_response.rb +13 -0
- data/lib/langchain/llm/response/mistral_ai_response.rb +39 -0
- data/lib/langchain/llm/response/ollama_response.rb +27 -1
- data/lib/langchain/llm/response/openai_response.rb +8 -0
- data/lib/langchain/loader.rb +3 -2
- data/lib/langchain/output_parsers/base.rb +0 -4
- data/lib/langchain/output_parsers/output_fixing_parser.rb +7 -14
- data/lib/langchain/output_parsers/structured_output_parser.rb +0 -10
- data/lib/langchain/processors/csv.rb +37 -3
- data/lib/langchain/processors/eml.rb +64 -0
- data/lib/langchain/processors/markdown.rb +17 -0
- data/lib/langchain/processors/pptx.rb +29 -0
- data/lib/langchain/prompt/loading.rb +1 -1
- data/lib/langchain/tool/base.rb +21 -53
- data/lib/langchain/tool/calculator/calculator.json +19 -0
- data/lib/langchain/tool/{calculator.rb → calculator/calculator.rb} +8 -16
- data/lib/langchain/tool/database/database.json +46 -0
- data/lib/langchain/tool/database/database.rb +99 -0
- data/lib/langchain/tool/file_system/file_system.json +57 -0
- data/lib/langchain/tool/file_system/file_system.rb +32 -0
- data/lib/langchain/tool/google_search/google_search.json +19 -0
- data/lib/langchain/tool/{google_search.rb → google_search/google_search.rb} +5 -15
- data/lib/langchain/tool/ruby_code_interpreter/ruby_code_interpreter.json +19 -0
- data/lib/langchain/tool/{ruby_code_interpreter.rb → ruby_code_interpreter/ruby_code_interpreter.rb} +8 -4
- data/lib/langchain/tool/vectorsearch/vectorsearch.json +24 -0
- data/lib/langchain/tool/vectorsearch/vectorsearch.rb +36 -0
- data/lib/langchain/tool/weather/weather.json +19 -0
- data/lib/langchain/tool/{weather.rb → weather/weather.rb} +3 -15
- data/lib/langchain/tool/wikipedia/wikipedia.json +19 -0
- data/lib/langchain/tool/{wikipedia.rb → wikipedia/wikipedia.rb} +9 -9
- data/lib/langchain/utils/token_length/ai21_validator.rb +6 -2
- data/lib/langchain/utils/token_length/base_validator.rb +1 -1
- data/lib/langchain/utils/token_length/cohere_validator.rb +6 -2
- data/lib/langchain/utils/token_length/google_palm_validator.rb +5 -1
- data/lib/langchain/utils/token_length/openai_validator.rb +55 -1
- data/lib/langchain/utils/token_length/token_limit_exceeded.rb +1 -1
- data/lib/langchain/vectorsearch/base.rb +11 -4
- data/lib/langchain/vectorsearch/chroma.rb +10 -1
- data/lib/langchain/vectorsearch/elasticsearch.rb +53 -4
- data/lib/langchain/vectorsearch/epsilla.rb +149 -0
- data/lib/langchain/vectorsearch/hnswlib.rb +5 -1
- data/lib/langchain/vectorsearch/milvus.rb +4 -2
- data/lib/langchain/vectorsearch/pgvector.rb +14 -4
- data/lib/langchain/vectorsearch/pinecone.rb +8 -5
- data/lib/langchain/vectorsearch/qdrant.rb +16 -4
- data/lib/langchain/vectorsearch/weaviate.rb +20 -2
- data/lib/langchain/version.rb +1 -1
- data/lib/langchain.rb +20 -5
- metadata +182 -45
- data/lib/langchain/agent/agents.md +0 -54
- data/lib/langchain/agent/base.rb +0 -20
- data/lib/langchain/agent/react_agent/react_agent_prompt.yaml +0 -26
- data/lib/langchain/agent/react_agent.rb +0 -131
- data/lib/langchain/agent/sql_query_agent/sql_query_agent_answer_prompt.yaml +0 -11
- data/lib/langchain/agent/sql_query_agent/sql_query_agent_sql_prompt.yaml +0 -21
- data/lib/langchain/agent/sql_query_agent.rb +0 -82
- data/lib/langchain/conversation/context.rb +0 -8
- data/lib/langchain/conversation/memory.rb +0 -86
- data/lib/langchain/conversation/message.rb +0 -48
- data/lib/langchain/conversation/prompt.rb +0 -8
- data/lib/langchain/conversation/response.rb +0 -8
- data/lib/langchain/conversation.rb +0 -93
- data/lib/langchain/tool/database.rb +0 -90
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f29aad35bc35dc95eb8673b11578b51c7449a19818989d9da5e640c6fb219c7
|
4
|
+
data.tar.gz: 4d0c4d3d424a82c7f02fb9e49ca52a5bdca5dfbce19fbfa22f2d74ef46d81eb7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
*
|
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
|
-
- [
|
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
|
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
|
57
|
-
| --------
|
58
|
-
| [OpenAI](https://openai.com
|
59
|
-
| [AI21](https://ai21.com
|
60
|
-
| [Anthropic](https://
|
61
|
-
| [AWS Bedrock](https://aws.amazon.com/bedrock)
|
62
|
-
| [Cohere](https://
|
63
|
-
| [GooglePalm](https://ai.google/discover/palm2
|
64
|
-
| [
|
65
|
-
| [
|
66
|
-
| [
|
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", "~>
|
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(
|
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.
|
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(
|
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
|
307
|
-
| --------
|
308
|
-
| [Chroma](https://trychroma.com
|
309
|
-
| [
|
310
|
-
| [
|
311
|
-
| [
|
312
|
-
| [
|
313
|
-
| [
|
314
|
-
| [
|
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
|
-
##
|
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
|
-
###
|
406
|
+
### Available Tools 🛠️
|
403
407
|
|
404
|
-
|
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
|
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
|
-
|
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
|
-
|
441
|
+
3. Instantiate an Assistant
|
414
442
|
```ruby
|
415
|
-
|
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
|
-
|
458
|
+
Run the Assistant to generate a response.
|
419
459
|
```ruby
|
420
|
-
|
460
|
+
assistant.run
|
421
461
|
```
|
422
462
|
|
423
|
-
|
463
|
+
If a Tool is invoked you can manually submit an output.
|
424
464
|
```ruby
|
425
|
-
|
426
|
-
|
427
|
-
|
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
|
-
|
478
|
+
### Accessing Thread messages
|
479
|
+
You can access the messages in a Thread by calling `assistant.thread.messages`.
|
431
480
|
```ruby
|
432
|
-
|
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
|
-
|
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
|