instruct 0.1.0a1
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 +7 -0
- data/LICENSE +202 -0
- data/README.md +387 -0
- data/SCRATCHPAD.md +489 -0
- data/lib/instruct/compile_erb.rb +39 -0
- data/lib/instruct/env.rb +27 -0
- data/lib/instruct/error.rb +4 -0
- data/lib/instruct/gen/completion_request.rb +63 -0
- data/lib/instruct/gen/completion_response.rb +66 -0
- data/lib/instruct/gen/gen.rb +70 -0
- data/lib/instruct/gen/generate_completion.rb +61 -0
- data/lib/instruct/helpers/erb_helper.rb +29 -0
- data/lib/instruct/helpers/gen_helper.rb +22 -0
- data/lib/instruct/helpers/helpers.rb +9 -0
- data/lib/instruct/helpers/model_helper.rb +13 -0
- data/lib/instruct/helpers/refinements.rb +54 -0
- data/lib/instruct/llms/anthropic/completion_model.rb +107 -0
- data/lib/instruct/llms/anthropic/messages_completion_response.rb +35 -0
- data/lib/instruct/llms/anthropic/middleware.rb +91 -0
- data/lib/instruct/llms/openai/chat_completion_response.rb +21 -0
- data/lib/instruct/llms/openai/completion_model.rb +129 -0
- data/lib/instruct/llms/openai/completion_response.rb +20 -0
- data/lib/instruct/llms/openai/middleware.rb +52 -0
- data/lib/instruct/middleware/chat_completion_middleware.rb +90 -0
- data/lib/instruct/middleware/chomp_middleware.rb +56 -0
- data/lib/instruct/model.rb +21 -0
- data/lib/instruct/prompt.rb +217 -0
- data/lib/instruct/rails/active_job_object_serializer.rb +23 -0
- data/lib/instruct/rails/active_record_coders.rb +36 -0
- data/lib/instruct/railtie.rb +15 -0
- data/lib/instruct/utils/middleware_chain.rb +48 -0
- data/lib/instruct/utils/serializable_with_version.rb +73 -0
- data/lib/instruct/utils/serializer.rb +70 -0
- data/lib/instruct/utils/symbolize_keys.rb +22 -0
- data/lib/instruct/utils/variables.rb +37 -0
- data/lib/instruct/version.rb +3 -0
- data/lib/instruct.rb +74 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 91c55f91575720976bb45e97f2526c6f0fee0f8b1dc841a74ddaefb343d6fa1b
|
4
|
+
data.tar.gz: 1c651ca4c3f8b733e07be1b6da4a2b89b9d2fac6343488341f892fdc7e7bb2a8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab7ea3e51f2e5402b612d80e31ce0e36603cff3cc4a36b957861680ba29d005ec3e9105716ef2722708f18c9846011e7ab180b11f865742c60298494058a35a0
|
7
|
+
data.tar.gz: 383a431acd033aa806fba1a488090a1a25579aeb3bb7c1f20963583111017190cd69136ffa8e788a146486dacfa6d5c658128d1ad02d4ed83cc0c3d2567d169d
|
data/LICENSE
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
Copyright 2024-, Andrew Mackross
|
2
|
+
Apache License
|
3
|
+
Version 2.0, January 2004
|
4
|
+
http://www.apache.org/licenses/
|
5
|
+
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
7
|
+
|
8
|
+
1. Definitions.
|
9
|
+
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
12
|
+
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
14
|
+
the copyright owner that is granting the License.
|
15
|
+
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
17
|
+
other entities that control, are controlled by, or are under common
|
18
|
+
control with that entity. For the purposes of this definition,
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
20
|
+
direction or management of such entity, whether by contract or
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
23
|
+
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
25
|
+
exercising permissions granted by this License.
|
26
|
+
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
28
|
+
including but not limited to software source code, documentation
|
29
|
+
source, and configuration files.
|
30
|
+
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
32
|
+
transformation or translation of a Source form, including but
|
33
|
+
not limited to compiled object code, generated documentation,
|
34
|
+
and conversions to other media types.
|
35
|
+
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
37
|
+
Object form, made available under the License, as indicated by a
|
38
|
+
copyright notice that is included in or attached to the work
|
39
|
+
(an example is provided in the Appendix below).
|
40
|
+
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
47
|
+
the Work and Derivative Works thereof.
|
48
|
+
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
50
|
+
the original version of the Work and any modifications or additions
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
62
|
+
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
65
|
+
subsequently incorporated within the Work.
|
66
|
+
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
73
|
+
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
79
|
+
where such license applies only to those patent claims licensable
|
80
|
+
by such Contributor that are necessarily infringed by their
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
83
|
+
institute patent litigation against any entity (including a
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
86
|
+
or contributory patent infringement, then any patent licenses
|
87
|
+
granted to You under this License for that Work shall terminate
|
88
|
+
as of the date such litigation is filed.
|
89
|
+
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
92
|
+
modifications, and in Source or Object form, provided that You
|
93
|
+
meet the following conditions:
|
94
|
+
|
95
|
+
(a) You must give any other recipients of the Work or
|
96
|
+
Derivative Works a copy of this License; and
|
97
|
+
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
99
|
+
stating that You changed the files; and
|
100
|
+
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
103
|
+
attribution notices from the Source form of the Work,
|
104
|
+
excluding those notices that do not pertain to any part of
|
105
|
+
the Derivative Works; and
|
106
|
+
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
109
|
+
include a readable copy of the attribution notices contained
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
112
|
+
of the following places: within a NOTICE text file distributed
|
113
|
+
as part of the Derivative Works; within the Source form or
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
115
|
+
within a display generated by the Derivative Works, if and
|
116
|
+
wherever such third-party notices normally appear. The contents
|
117
|
+
of the NOTICE file are for informational purposes only and
|
118
|
+
do not modify the License. You may add Your own attribution
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
121
|
+
that such additional attribution notices cannot be construed
|
122
|
+
as modifying the License.
|
123
|
+
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
125
|
+
may provide additional or different license terms and conditions
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
129
|
+
the conditions stated in this License.
|
130
|
+
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
134
|
+
this License, without any additional terms or conditions.
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
136
|
+
the terms of any separate license agreement you may have executed
|
137
|
+
with Licensor regarding such Contributions.
|
138
|
+
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
141
|
+
except as required for reasonable and customary use in describing the
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
143
|
+
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
153
|
+
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
159
|
+
incidental, or consequential damages of any character arising as a
|
160
|
+
result of this License or out of the use or inability to use the
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
163
|
+
other commercial damages or losses), even if such Contributor
|
164
|
+
has been advised of the possibility of such damages.
|
165
|
+
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
169
|
+
or other liability obligations and/or rights consistent with this
|
170
|
+
License. However, in accepting such obligations, You may act only
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
175
|
+
of your accepting any such warranty or additional liability.
|
176
|
+
|
177
|
+
END OF TERMS AND CONDITIONS
|
178
|
+
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
180
|
+
|
181
|
+
To apply the Apache License to your work, attach the following
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
183
|
+
replaced with your own identifying information. (Don't include
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
185
|
+
comment syntax for the file format. We also recommend that a
|
186
|
+
file or class name and description of purpose be included on the
|
187
|
+
same "printed page" as the copyright notice for easier
|
188
|
+
identification within third-party archives.
|
189
|
+
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
191
|
+
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
193
|
+
you may not use this file except in compliance with the License.
|
194
|
+
You may obtain a copy of the License at
|
195
|
+
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
197
|
+
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
201
|
+
See the License for the specific language governing permissions and
|
202
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,387 @@
|
|
1
|
+
# Instruct
|
2
|
+
|
3
|
+
🏗️ **This gem is still undergoing active development and is not yet ready for use
|
4
|
+
beyond experimentation.**
|
5
|
+
|
6
|
+
I'm making this public to get feedback and to see if there is any interest in
|
7
|
+
from the community to help develop this further.
|
8
|
+
|
9
|
+
---
|
10
|
+
|
11
|
+
*Instruct LLMs to do what you want in Ruby.*
|
12
|
+
|
13
|
+
Combine **code**, **prompts**, and **completions** in a natural and intuitive
|
14
|
+
way for programmers. Inspired by libraries like
|
15
|
+
[Guidance](https://github.com/guidance-ai/guidance) and rack, Instruct strips
|
16
|
+
away boilerplate code while providing a flexible and powerful interface that
|
17
|
+
doesn't abstract away control.
|
18
|
+
|
19
|
+
|
20
|
+
## Features
|
21
|
+
|
22
|
+
* **Natural and Intuitive API**
|
23
|
+
Using LLMs with instruct is not too different from plain old string
|
24
|
+
manipulation. This lets you think about your prompts and completions in a way
|
25
|
+
that intuitively makes sense to most programmers.
|
26
|
+
* **Safe Prompting**
|
27
|
+
The ERB `#p{}`rompt helper can be used to generate prompts with dynamic input in an
|
28
|
+
familiar way. Dynamic input is automatically marked as unsafe and can be
|
29
|
+
handled differently by middleware (for example to check for prompt
|
30
|
+
injections). Use `.prompt_safe` to mark part of the prompt as safe.
|
31
|
+
* **Flexible Middleware Stack**
|
32
|
+
Middleware can be used to add features like structured output, conversation
|
33
|
+
pruning, RAG integrations, retries, auto-continuation, guard-rails, monitoring
|
34
|
+
and more. The middleware stack provides a common way to transform a prompt for
|
35
|
+
different LLM models with different capabilities.
|
36
|
+
* **Streaming Support**
|
37
|
+
Both middleware and callers can process completion responses as the chunks
|
38
|
+
arrive. This can be used to display a completion in real time, or to validate
|
39
|
+
or parse the output of an LLM call as it's being generated.
|
40
|
+
* **Rails Integration**
|
41
|
+
Prompts, completions and models can be serialized and stored on ActiveRecord
|
42
|
+
with custom attributes and will automatically serialize when passed to an
|
43
|
+
ActiveJob. Enabling easy background processing of LLM calls.
|
44
|
+
|
45
|
+
## Installation
|
46
|
+
|
47
|
+
This gem won't be published to RubyGems until it's more stable. For now, you can
|
48
|
+
add these lines to your application's Gemfile:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
gem "instruct", github: "instruct-rb/instruct", branch: "development"
|
52
|
+
gem "attributed-string", github: "instruct-rb/attributed-string", branch: "main"
|
53
|
+
```
|
54
|
+
|
55
|
+
Include the helpers and refinements in the modules or classes where you want to
|
56
|
+
use Instruct.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
include Instruct::Helpers
|
60
|
+
using Instruct::Refinements
|
61
|
+
```
|
62
|
+
|
63
|
+
Instruct supports the ruby-openai gem and anthropic out of the box, simply include the
|
64
|
+
one or both gems in your Gemfile.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
gem "ruby-openai"
|
68
|
+
gem "anthropic"
|
69
|
+
```
|
70
|
+
|
71
|
+
For more info on setting up the OpenAI or Anthropic clients, see the docs for
|
72
|
+
[OpenAI Usage](docs/openai-usage.md) and [Anthropic Usage](docs/anthropic-usage.md).
|
73
|
+
|
74
|
+
## Usage
|
75
|
+
|
76
|
+
### The gen function
|
77
|
+
|
78
|
+
`gen` is used to **gen**erate completions from an LLM.
|
79
|
+
|
80
|
+
### Simple Usage
|
81
|
+
|
82
|
+
Getting a single completion from an LLM is as simple as calling `gen` with a prompt.
|
83
|
+
|
84
|
+
When a prompt is a present as the first argument the gen call immediately
|
85
|
+
retrieves the completion from the LLM.
|
86
|
+
|
87
|
+
The model can be set with the model keyword argument if no default model has been
|
88
|
+
set. Similarly all arguments that `ruby-openai` and `anthropic` can be configured with
|
89
|
+
can be passed into the gen call.
|
90
|
+
```ruby
|
91
|
+
completion = gen("The capital of France is ", stop_chars: "\n ,.", model: 'gpt-3-5-turbo-instruct')
|
92
|
+
|
93
|
+
puts completion # => "Paris"
|
94
|
+
```
|
95
|
+
|
96
|
+
### Deferred Completions
|
97
|
+
|
98
|
+
The gen function can also create deferred completions. This is used to create
|
99
|
+
prompts that can be called multiple times or passed around as an argument.
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
|
103
|
+
# Adding gen to a string creates a deferred completion. This is indicated by
|
104
|
+
# the 💬 emoji.
|
105
|
+
prompt = "The capital of France is " + gen(stop_chars: "\n ,;.") + "!"
|
106
|
+
|
107
|
+
puts prompt # => "The capital of France is 💬!"
|
108
|
+
|
109
|
+
# Each time the prompt is called, a new completion is generated and returned.
|
110
|
+
completion = prompt.call
|
111
|
+
|
112
|
+
puts completion # => "Paris"
|
113
|
+
|
114
|
+
# When a completion is added to the prompt that generated it, a new
|
115
|
+
# prompt is created with the completion replacing the deferred completion.
|
116
|
+
puts prompt + completion # => "The capital of France is Paris!"
|
117
|
+
|
118
|
+
# Note the exclamation mark is still present and comes after the completion.
|
119
|
+
```
|
120
|
+
|
121
|
+
### Appending to an existing prompt
|
122
|
+
|
123
|
+
The double angle bracket operator `<<` can be used to quickly append objects
|
124
|
+
to a prompt. This can be used to modify and build up a prompt in place.
|
125
|
+
|
126
|
+
Unlike the `+=` and `concat` operators, the `<<` operator will immediately call any
|
127
|
+
deferred completions and append them to the prompt.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
string = Instruct::Prompt.new
|
131
|
+
string << "The capital of France is "
|
132
|
+
string << gen(stop_chars: "\n ,;.") + "!"
|
133
|
+
|
134
|
+
puts string # => "The capital of France is Paris!"
|
135
|
+
|
136
|
+
string << " It is widely known for " + gen(stop_chars: ".") + "."
|
137
|
+
puts string # => "The capital of France is Paris! It is widely known for its fashion, art and culture."
|
138
|
+
```
|
139
|
+
|
140
|
+
### Capturing Generated Completion
|
141
|
+
|
142
|
+
Because it's quite common to want to access a completion, but not break apart
|
143
|
+
the prompt and completion into separate components, instruct provides `capture`
|
144
|
+
captures the result of a completion from a deferred generation and makes it
|
145
|
+
accessible from the prompt with `captured`.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
string = Instruct::Prompt.new
|
149
|
+
string << "The capital of France is " + gen(stop: '\n','.').capture(:capital)
|
150
|
+
|
151
|
+
puts string.captured(:capital) # => "Paris"
|
152
|
+
```
|
153
|
+
|
154
|
+
Passing a `list: :key` keyword argument will capture an array of completions under the same key.
|
155
|
+
|
156
|
+
### Creating a Prompt Transcript
|
157
|
+
|
158
|
+
Most modern LLMs are designed for conversational style completions. The chat
|
159
|
+
completion middleware transforms a prompt formatted like a transcript into an
|
160
|
+
object that can be used with these APIs.
|
161
|
+
```ruby
|
162
|
+
# Roles can be added to a prompt transcript by a new line with the role name
|
163
|
+
# followed by a colon and then a space.
|
164
|
+
transcript = p{"
|
165
|
+
system: You're an expert geographer that speaks only French
|
166
|
+
user: What is the capital of Australia?
|
167
|
+
"} + gen(prompt, stop_chars: "\n ,;.", model: 'gpt-4o')
|
168
|
+
|
169
|
+
|
170
|
+
# Note the returned or captured completion does not include any role prefix.
|
171
|
+
completion = transcript.call
|
172
|
+
puts completion # => "le capital de l'Australie est Canberra"
|
173
|
+
|
174
|
+
# However, when the completion is added to the transcript, the `assistant: `
|
175
|
+
# prefix is automatically prepended (if required), enabling a new user prompt
|
176
|
+
# to be appended immediately after.
|
177
|
+
puts transcript + completion
|
178
|
+
# => "system: You're an expert geographer that speaks only French
|
179
|
+
# user: What is the capital of Australia?
|
180
|
+
# assistant: le capital de l'Australie est Canberra"
|
181
|
+
```
|
182
|
+
|
183
|
+
If you want to be more explicit about adding roles in to a prompt, instruct provides
|
184
|
+
`#p.system`, `#p.user`, and `#p.assistant` helper methods. There is nothing
|
185
|
+
special about these methods, they just prepend the role prefix to the string
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
transcript = p.system{"You're an expert geographer that speaks only French"}
|
189
|
+
transcript += p.user{"What is the capital of Australia?"}
|
190
|
+
transcript += gen(stop_chars: "\n ,;.", model: 'gpt-4o')
|
191
|
+
```
|
192
|
+
|
193
|
+
### The p(rompt) Block ERB Helper
|
194
|
+
|
195
|
+
`#p{}` (shown above) allows for dynamic prompt templating using ERB tags
|
196
|
+
`<%= %>` with automatic handling of safe and unsafe content similar to HTML
|
197
|
+
templating.
|
198
|
+
|
199
|
+
This safety mechanism provides a way for both programmer and middleware to tell
|
200
|
+
the difference between user, LLM, and prompt template text. In the case of the
|
201
|
+
Chat Completion Middleware, role switches cannot occur in unsafe text.
|
202
|
+
|
203
|
+
Similarly, guard middleware might be added to check unsafe content for prompt
|
204
|
+
injections or innapropriate content.
|
205
|
+
|
206
|
+
ERB heredoc blocks combined with the `p` helper provide syntax highlighting
|
207
|
+
in most editors making long dynamic prompts easy to read. The following prompt
|
208
|
+
shows how to use a chomped ERB heredoc to generate larger prompts with both
|
209
|
+
"safe" and "unsafe" content.
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
p = p{<<~ERB.chomp
|
213
|
+
This is a longer prompt, if the included content might include
|
214
|
+
injections use the normal ERB tags like so: <%= unsafe_user_content %>.
|
215
|
+
|
216
|
+
If we know that something doesn't include prompt injections, add it
|
217
|
+
as: <%= raw some_safe_content %>, #{some_safe_content} or <%=
|
218
|
+
some_safe_string.prompt_safe %>.
|
219
|
+
|
220
|
+
By default generated LLM responses as <%= gen %> or #{ gen } will be added
|
221
|
+
to the prompt as unsafe. To add it as safe we cannot use the ERB
|
222
|
+
method, we instead need to call .prompt_safe on the completion befored
|
223
|
+
appending it.
|
224
|
+
ERB
|
225
|
+
}
|
226
|
+
```
|
227
|
+
|
228
|
+
### A More Complex Example: Multi-Turn Conversations Between Agents
|
229
|
+
|
230
|
+
Here we put together all the features so far to show how you can easily manage multi-turn
|
231
|
+
interactions between two different agents.
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
# Create two agents: Noel Gallagher and an interviewer with a system prompt.
|
235
|
+
noel = p.system{"You're Noel Gallagher. Answer questions from an interviewer."}
|
236
|
+
interviewer = p.system{"You're a skilled interviewer asking Noel Gallagher questions."}
|
237
|
+
|
238
|
+
# We start a dynamic Q&A loop with the interviewer by kicking off the
|
239
|
+
# interviewing agent and capturing the response under the :reply key.
|
240
|
+
interviewer << p.user{"__Noel sits down in front of you.__"} + gen.capture(:reply)
|
241
|
+
|
242
|
+
puts interviewer.captured(:reply) # => "Hello Noel, how are you today?"
|
243
|
+
|
244
|
+
5.times do
|
245
|
+
# Noel is sent the last value captured in the interviewer's transcript under the :reply key.
|
246
|
+
# Similarly, we generate a response for Noel and capture it under the :reply key.
|
247
|
+
noel << p.user{"<%= interviewer.captured(:reply) %>"} + gen.capture(:reply, list: :replies)
|
248
|
+
|
249
|
+
# Noel's captured reply is now sent to the interviewer, who captures it in the same way.
|
250
|
+
interviewer << p.user{"<%= noel.captured(:reply) %>"} + gen.capture(:reply, list: :replies)
|
251
|
+
end
|
252
|
+
|
253
|
+
# After the conversation, we can access the list captured replies from both agents
|
254
|
+
noel_said = noel.captured(:replies).map{ |r| "noel: #{r}" }
|
255
|
+
interviewer_said = interviewer.captured(:replies).map{ |r| "interviewer: #{r}" }
|
256
|
+
|
257
|
+
puts interviwer_said.zip(noel_said).flatten.join("\n\n")
|
258
|
+
# => "noel: ... \n\n interviewer: ..., ..."
|
259
|
+
```
|
260
|
+
|
261
|
+
## The Prompt
|
262
|
+
The following examples illustrate how the Prompt can be manipulated in
|
263
|
+
different ways.
|
264
|
+
``` ruby
|
265
|
+
Instruct::Prompt.new << "The capital of France is" + gen(stop: '\n','.')
|
266
|
+
# => "The capital of France is Paris"
|
267
|
+
|
268
|
+
prompt = "The capital of France is" + gen(stop: '\n','.')
|
269
|
+
# => "The capital of France is 💬"
|
270
|
+
# Note that a prompt can just be created by adding a string and a gen
|
271
|
+
# call. However, the gen call is deferred (as indicated by the 💬).
|
272
|
+
|
273
|
+
prompt.class
|
274
|
+
# => Instruct::Prompt
|
275
|
+
|
276
|
+
result = prompt.call do |response|
|
277
|
+
# This optional block on the call method can be used for streaming
|
278
|
+
# The response is called after each chunk is processed by the middleware,
|
279
|
+
# the response is the entire buffer so Paris as three chunks might look like
|
280
|
+
# "P", "Par", "Paris". It's possible that middleware could change the
|
281
|
+
# response, so it's best not to treat these as final until after the call is
|
282
|
+
# finished.
|
283
|
+
end
|
284
|
+
# => "Paris"
|
285
|
+
|
286
|
+
result.class
|
287
|
+
# => Instruct::Prompt::Completion
|
288
|
+
|
289
|
+
result.prompt
|
290
|
+
# => "The capital of France is 💬"
|
291
|
+
|
292
|
+
|
293
|
+
result.prompt == prompt
|
294
|
+
# => true
|
295
|
+
|
296
|
+
together = prompt + result
|
297
|
+
# => "The capital of France is Paris"
|
298
|
+
# Adding a completion to a prompt will return the prompt with the completion appended.
|
299
|
+
# If this completion was generated using the same prompt, it will also update the prompts
|
300
|
+
# content with any additional changes that were made by middleware during
|
301
|
+
# the call that produced the completion. This includes transferring the captured values.
|
302
|
+
|
303
|
+
together.class
|
304
|
+
# => Instruct::Prompt
|
305
|
+
|
306
|
+
# This does nothing as there are no deferred calls.
|
307
|
+
together.call
|
308
|
+
# => nil
|
309
|
+
|
310
|
+
prompt = "The capital of Germany is" + gen(stop: '\n','.') + ", which is in the region of " + gen(stop: '\n','.')
|
311
|
+
# => "The capital of Germany is 💬, which is in the region of 💬"
|
312
|
+
|
313
|
+
result = prompt.call
|
314
|
+
# => [ "Berlin", "Europe" ] # Array<Instruct::Prompt::Completion>
|
315
|
+
|
316
|
+
new_prompt == Instruct::Serializer.load(Instruct::Serializer.dump(prompt))
|
317
|
+
# => "The capital of Germany is 💬, which is in the region of 💬"
|
318
|
+
|
319
|
+
new_prompt == prompt
|
320
|
+
# => true
|
321
|
+
|
322
|
+
# The results are joined together with the prompt in the order they were returned.
|
323
|
+
together = new_prompt + result
|
324
|
+
# => "The capital of Germany is Berlin, which is in the region of Europe"
|
325
|
+
|
326
|
+
# The interpolation only occurs if the prompt that generated the completion(s)
|
327
|
+
# is equal to the prompt that is being added or concatenated to. In all other
|
328
|
+
# cases the completion is added to the end of the prompt.
|
329
|
+
```
|
330
|
+
|
331
|
+
## Logging Setup
|
332
|
+
`Instruct.error_logger` and `Instruct.logger` can be set to any ruby `Logger`
|
333
|
+
class. By default they are configured to log warn and error messages. Set the `INSTRUCT_LOG_LEVEL`
|
334
|
+
environment variable to `debug`, `info`, `warn`, `error`, `fatal`, `unknown` to change the
|
335
|
+
the log level, or change the log level directly on the logger instance.
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
# logs errors and warnings to STDERR by default, by default all warnings and
|
339
|
+
# errors are logged
|
340
|
+
Instruct.err_logger.sev_threshold = :warn
|
341
|
+
|
342
|
+
# logs all debug and info messages to STDOUT, by default nothing is logged as
|
343
|
+
# the default is warn.
|
344
|
+
Instruct.logger.sev_threshold = :warn
|
345
|
+
```
|
346
|
+
|
347
|
+
|
348
|
+
## What's missing
|
349
|
+
This gem is still in development and is missing many features before a 1.0,
|
350
|
+
please feel free to get in touch if you would like to contribute or have any
|
351
|
+
ideas.
|
352
|
+
|
353
|
+
- Middleware
|
354
|
+
- [ ] Constraint based validation with automatic retries
|
355
|
+
- [ ] Improvments to chat completion middleware
|
356
|
+
- [ ] Allow role switching on the same line but then in the updated prompt fix it
|
357
|
+
- [ ] New Conversation middleware with default to user with system kw arg or assistant kw arg (maybe its one and the same?)
|
358
|
+
- [ ] Conversation management (prune long running conversations)
|
359
|
+
- [ ] Async support (waiting on async support in ruby-openai). This enables
|
360
|
+
the use of async calls to the LLM and the use of async middleware.
|
361
|
+
- [ ] Streaming structured output (similar to BAML or a CFG)
|
362
|
+
- [ ] Self healing
|
363
|
+
- [ ] Guard-rails (middleware that checks for prompt injections/high perplexity)
|
364
|
+
- [ ] Auto-continuation (middleware that adds prompts to continue a conversation)
|
365
|
+
- [ ] Support transform attachments in the prompt intos multi-modal input
|
366
|
+
- [ ] Anthropic caching
|
367
|
+
- [ ] Visualize streaming prompt as a tree in web interface (dependent on forking)
|
368
|
+
- [ ] Standardize finish reasons, and shared call arguments
|
369
|
+
- Models
|
370
|
+
- [x] OpenAI API model selection
|
371
|
+
- [x] Anthropic API model selection
|
372
|
+
- [ ] Gemini models
|
373
|
+
- [ ] Local models
|
374
|
+
- [ ] Constrained inference like Guidance
|
375
|
+
- [ ] Token healing
|
376
|
+
- Core
|
377
|
+
- [ ] Track forking path
|
378
|
+
- [ ] Change middleware by passing it into the gen or call methods
|
379
|
+
- [ ] Tool calling
|
380
|
+
- [ ] Develop an intuitive API for calling tools
|
381
|
+
- [ ] Batch APIs
|
382
|
+
- [ ] Improve attributed string API with a visitor style presenter
|
383
|
+
- [ ] Update middleware and printers to use the new presenters
|
384
|
+
- [x] Serialization of prompts (Consider migrations / upgrades) for storage
|
385
|
+
- [x] Register ActiveJob serializer for prompts so that they can be added to the job queue
|
386
|
+
- [ ] Register ActiveRecord serializer for prompts so that they can be stored in the database
|
387
|
+
- [x] `stop_chars` and `stop`
|