Skip to content

Commit

Permalink
Support interpolation into prompt-based function
Browse files Browse the repository at this point in the history
* functions of type prompts can use the parameters
  in their prompts
  • Loading branch information
slimslenderslacks committed Sep 12, 2024
1 parent 39e0e35 commit 3ebaf6a
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 59 deletions.
3 changes: 1 addition & 2 deletions graphs/prompts/pages/Authoring Prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ A prompt file can also contain metadata about how to generate and run the prompt
{{embed ((66d7f3ff-8769-40b3-b6b5-fc4fceea879e)) }}
The example above defines a lot of metadata. A prompt file might also be very simple.
{{embed ((66d8a396-9268-4917-882f-da4c52b7b5dd)) }}
Use the [prompt engine]([[Running the Prompt Engine]]) to run this prompt file against an LLM.
-
Use the [prompt engine]([[Running the Prompt Engine]]) to run this prompt file against an LLM.
77 changes: 74 additions & 3 deletions graphs/prompts/pages/Tools.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,78 @@
# Using a Tool
A [[tool]] augments an LLM assistant by providing it what capabilities in the real world.
By adding tools to prompts during the [authoring process]([[Authoring Prompts]]), our assistant can start to incorporate these new capabilities into a [[conversation loop]]. Here's a prompt that incorporates a tool into the conversation ((66da266d-79cb-489e-afa3-d205619b6f3e)).
By adding tools to prompts during the [authoring process]([[Authoring Prompts]]), our assistant can start to incorporate these new capabilities into a [[conversation loop]]. Here's a prompt that incorporates a tool into the conversation ((66da266d-79cb-489e-afa3-d205619b6f3e))
- # Developing Tools
- ## Containerized Tools can share data
- ## Containerized Tools
- All tool definition have `name`, `description` and `parameters` fields. This is the data that is passed to the LLM so that it can request access to the underlying tool.
A _container_ tool also has a `container` field which defines how the [prompt engine]([[Running the Prompt Engine]]) should run the tool. Here is a sample container tool definition.
```
---
tools:
- name: sqlite
description: run the sqlite command
parameters:
type: object
properties:
database:
type: string
description: the path to the database
sql:
type: string
description: the sql statement to run
container:
image: vonwig/sqlite:latest
command:
- "{{database}}"
- "{{sql|safe}}"
---
```
- only the `image` field is mandatory but the `command` `entrypoint` and `env` fields are also supported.
- all of the fields support interpolation using `"{{parameter}}"` syntax. This allows us to define tools that will be invoked using parameters bound by the LLM.
- the `"{{sql|safe}}"` syntax above is a django-inspired reference which means that the `sql` string is passed through a `safe` filter. (TODO: describe the concept of parameter safety)
- ### Containerized Tools can share data
- Every _tool_ container will automatically run with a volume mounted into the container at `/thread`. This means that within the scope of a [conversation loop](conversation-loop), different tools can share data using this mount point.
The data is ephemeral so by default, the volume is deleted after each execution of the prompt engine. If you pass the argument `--thread-id {volume_name}` to the prompt engine then the volume will be preserved for inspection.
The data is ephemeral so by default, the volume is deleted after each execution of the prompt engine. If you pass the argument `--thread-id {volume_name}` to the prompt engine then the volume will be preserved for inspection.
- ### Containerized Tools can access parts of the host file system
- Every _tool_ container will have one host directory mounted into the container at `/project`. By default, this mount is `rw` but a tool definition can specify `project: ro` or `project: none` in the container definition. This is encouraged when a tool will not need access to a project's file system. The [prompt engine]([[Running the Prompt Engine]]) is always started with a `--host-dir` parameter which defines what part of the file system the assistant can access during the run.
- ## Prompt-based Tools
- Besides tools that run in containers, we can also build a tool interface from another prompt. The definition of prompt-based tool continues to have the `name`, `description` and `parameters` fields but it also must a `type: prompt` and a mandatory `ref` field that points at the prompt file checked in to GitHub.
```
---
tools:
- name: fix-pylint-violation
description: fix a pylint violation
parameters:
type: object
properties:
code:
type: string
description: the code block
violation:
type: string
description: the description of the violation
type: "prompt"
ref: "github:docker/labs-ai-tools-for-devs?path=prompts/pylint/fix-violation.md"
---
```

When defining a `prompt` type tool, the parameters will be available to be interpolated into the prompt definition before being sent to the model. For example, the above function definition has parameters `code` and `violation` so the prompt file itself can reference those using moustache template syntax. For example the prompt file reference above could look like the file below (note the reference `{{code}}` and `{{violation}}` )

```
# prompt user
## Original Code
{{code}}
## Pylint Output
{{violation}}
**STRICTLY FOLLOW THE RULES BELOW:**
generate new code which will resolve the violation
Return the response in the following format:
```python
<corrected code>
```

Instead of a `ref` in the above definition, users can also define a `prompt-file` which must be a relative path (relative the prompt file that was originally passed to the engine). This `prompt-file` field is useful for developing prompts, because we can use local definitions that have not yet been pushed to git.
```
5 changes: 2 additions & 3 deletions prompts/pylint/7-1-fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ tools:
violation:
type: string
description: the description of the violation
prompt:
ref: github:docker/labs-ai-tools-for-devs?path=prompts/pylint/fix-violation.md
- name: sqlite
type: "prompt"
ref: "github:docker/labs-ai-tools-for-devs?path=prompts/pylint/fix-violation.md"
---

# prompt user
Expand Down
1 change: 0 additions & 1 deletion prompts/pylint/fix-violation.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# prompt user

## Original Code
Expand Down
102 changes: 52 additions & 50 deletions src/prompts.clj
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@
(defn get-prompts
"run extractors and then render prompt templates
returns ordered collection of chat messages"
[{:keys [prompts user platform] :as opts}]
[{:keys [parameters prompts user platform] :as opts}]
(let [;; TODO the docker default no longer makes sense here
m (run-extractors opts)
m (merge (run-extractors opts) parameters)
renderer (partial selma-render prompts (facts m user platform))
prompts (if (fs/directory? prompts)
(->> (fs/list-dir prompts)
Expand Down Expand Up @@ -207,54 +207,56 @@
(->> (filter #(= function-name (-> % :function :name)) functions)
first)
:function)]
(try
(cond
(:container definition) ;; synchronous call to container function
(let [arg-context (merge
;; TODO raw is a bad name when merging
{:raw (if json-arg-string
json-arg-string
"{}")}
(when json-arg-string (json/parse-string json-arg-string true)))
function-call (merge
(:container definition)
(dissoc opts :functions)
{:command (into []
(concat
[]
(->>
(-> definition :container :command)
(map (partial interpolate arg-context))
(into []))))}
(when user {:user user})
(when pat {:pat pat})
(when timeout {:timeout timeout}))
{:keys [pty-output exit-code done] :as result} (docker/run-function function-call)]
(cond
(and (= :exited done) (= 0 exit-code))
(resolve pty-output)
(and (= :exited done) (not= 0 exit-code))
(fail (format "call exited with non-zero code (%d): %s" exit-code pty-output))
(= :timeout done)
(fail (format "call timed out: %s" (:timeout result)))
:else
(fail (format "call failed"))))
(= "prompt" (:type definition)) ;; asynchronous call to another agent - new conversation-loop
;; TODO set a custom map for prompts in the next conversation loop
(do
(jsonrpc/notify :message {:content (format "## (%s) sub-prompt" (:ref definition))})
(let [{:keys [messages _finish-reason]}
(async/<!! (conversation-loop
(assoc opts :prompts (git/prompt-file (:ref definition)))))]
(jsonrpc/notify :message {:content (format "## (%s) end sub-prompt" (:ref definition))})
(resolve (->> messages
(filter #(= "assistant" (:role %)))
(last)
:content))))
:else
(fail (format "bad container definition %s" definition)))
(catch Throwable t
(fail (format "system failure %s" t))))
(let [arg-context (merge
;; TODO raw is a bad name when merging
{:raw (if json-arg-string
json-arg-string
"{}")}
(when json-arg-string (json/parse-string json-arg-string true)))]
(try
(cond
(:container definition) ;; synchronous call to container function
(let [function-call (merge
(:container definition)
(dissoc opts :functions)
{:command (into []
(concat
[]
(->>
(-> definition :container :command)
(map (partial interpolate arg-context))
(into []))))}
(when user {:user user})
(when pat {:pat pat})
(when timeout {:timeout timeout}))
{:keys [pty-output exit-code done] :as result} (docker/run-function function-call)]
(cond
(and (= :exited done) (= 0 exit-code))
(resolve pty-output)
(and (= :exited done) (not= 0 exit-code))
(fail (format "call exited with non-zero code (%d): %s" exit-code pty-output))
(= :timeout done)
(fail (format "call timed out: %s" (:timeout result)))
:else
(fail (format "call failed"))))

(= "prompt" (:type definition)) ;; asynchronous call to another agent - new conversation-loop
(do
(jsonrpc/notify :message {:content (format "## (%s) sub-prompt" (:ref definition))})
(let [{:keys [messages _finish-reason]}
(async/<!! (conversation-loop
(assoc opts
:prompts (git/prompt-file (:ref definition))
:parameters arg-context)))]
(jsonrpc/notify :message {:content (format "## (%s) end sub-prompt" (:ref definition))})
(resolve (->> messages
(filter #(= "assistant" (:role %)))
(last)
:content))))
:else
(fail (format "bad container definition %s" definition)))
(catch Throwable t
(fail (format "system failure %s" t)))))
(fail "no function found")))

(defn- run-prompts
Expand Down

0 comments on commit 3ebaf6a

Please sign in to comment.