From 0d13e18246fbac32e8a433bffb3af6de6ee2f919 Mon Sep 17 00:00:00 2001 From: Jim Clark Date: Fri, 24 Jan 2025 18:25:22 -0800 Subject: [PATCH] qrencode accepts arguments --- prompts/examples/qrencode.md | 17 ++++++++++++----- src/graph.clj | 2 +- src/jsonrpc/db.clj | 1 + src/jsonrpc/server.clj | 21 ++++++++------------- src/prompts.clj | 36 ++++++++++++++++++++++++++++++------ 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/prompts/examples/qrencode.md b/prompts/examples/qrencode.md index 4cd592a..86808e8 100644 --- a/prompts/examples/qrencode.md +++ b/prompts/examples/qrencode.md @@ -1,13 +1,20 @@ --- +name: qrencode +description: Generate QR codes tools: - name: qrencode +arguments: + - name: content + description: the content to encode in the QR code + required: true +parameter-values: + content: https://github.com/docker/labs-ai-tools-for-devs --- -# Prompt user +# prompt -Generate a QR code for the content -'https://github.com/docker/labs-ai-tools-for-devs'. -Save the generated image to `qrcode.png`. +Generate a QR code for the content '{{content}}'. +Save the generated image to `/thread/resources/qrcode.png`. If the command fails, read the man page and try again. If successful, output the path to the generated image in markdown syntax. @@ -16,6 +23,6 @@ If successful, output the path to the generated image in markdown syntax. After running the above prompt, there should be a file named `qrcode.png` in the current project's host directory. ```bash -open ~/vonwig/altaservice/qrcode.png +open ~/docker/labs-ai-tools-for-devs/qrcode.png ``` diff --git a/src/graph.clj b/src/graph.clj index d8a5b68..11ecc43 100644 --- a/src/graph.clj +++ b/src/graph.clj @@ -48,7 +48,7 @@ (let [[chunk-handler sample] (providers (llm-provider (or (:model metadata) model))) [c h] (chunk-handler) request (merge - (dissoc metadata :agent :host-dir :workdir :prompt-format :description :name :parameter-values) ; TODO should we just select relevant keys instead of removing bad ones + (dissoc metadata :agent :host-dir :workdir :prompt-format :description :name :parameter-values :arguments) ; TODO should we just select relevant keys instead of removing bad ones {:messages messages :level level} (when (seq functions) {:tools functions}) diff --git a/src/jsonrpc/db.clj b/src/jsonrpc/db.clj index b1c98c7..a04eb17 100644 --- a/src/jsonrpc/db.clj +++ b/src/jsonrpc/db.clj @@ -38,6 +38,7 @@ (add {:register ["github:docker/labs-ai-tools-for-devs?path=prompts/examples/explain_dockerfile.md" "github:docker/labs-ai-tools-for-devs?path=prompts/examples/hello_world.md"]})) +#_{:clj-kondo/ignore [:redefined-var]} (defn merge [{:keys [registry-content] :as opts}] (logger/info "adding dynamic prompts" registry-content) (try diff --git a/src/jsonrpc/server.clj b/src/jsonrpc/server.clj index 97d4f8f..7b9dc29 100644 --- a/src/jsonrpc/server.clj +++ b/src/jsonrpc/server.clj @@ -74,8 +74,8 @@ ;; merges client-info capabilities and client protocol-version (swap! db* merge params) {:protocol-version "2024-11-05" - :capabilities {:prompts {} - :tools {}} + :capabilities {:prompts {:listChanged true} + :tools {:listChanged true}} :server-info {:name "docker-mcp-server" :version "0.0.1"}}) @@ -96,29 +96,24 @@ (defn entry->prompt-listing [k v _messages] (merge {:name (str k)} - (select-keys (:metadata v) [:description]))) + (select-keys (:metadata v) [:description :arguments]))) (defmethod lsp.server/receive-request "prompts/list" [_ {:keys [db*]} params] ;; TODO might contain a cursor (logger/info "prompts/list" params) (let [prompts {:prompts (->> (:mcp.prompts/registry @db*) - (mapcat (fn [[k v]] (map (partial entry->prompt-listing k v) (:messages v)))) + (mapcat + (fn [[k v]] (map (partial entry->prompt-listing k v) (:messages v)))) (into []))}] (logger/info prompts) prompts)) -(defmethod lsp.server/receive-request "prompts/get" [_ {:keys [db*]} {:keys [name]}] - ;; TODO resolve arguments +(defmethod lsp.server/receive-request "prompts/get" [_ {:keys [db*]} {:keys [name arguments]}] (logger/info "prompts/get " name) - (let [{:keys [messages metadata] :as entry} (-> @db* :mcp.prompts/registry (get name))] + (let [{:keys [prompt-function metadata]} (-> @db* :mcp.prompts/registry (get name))] {:description (or (:description metadata) name) - :messages (->> messages - (map (fn [m] (-> m - (update :content (fn [content] - {:type "text" - :text content}))))) - (into []))})) + :messages (prompt-function (or arguments {}))})) (defmethod lsp.server/receive-request "resources/list" [_ _ _] {:resources []}) diff --git a/src/prompts.clj b/src/prompts.clj index a2b9af8..33a906b 100644 --- a/src/prompts.clj +++ b/src/prompts.clj @@ -154,9 +154,20 @@ (def prompt-file-pattern #".*_(.*)_.*.md") +(defn ->message [{:keys [content] :as m}] (assoc m :content {:text content :type "text"})) + (defn get-prompts - "run extractors and then render prompt templates - returns map of messages, functions, metadata and optionally error" + "run extractors and then render prompt templates (possibly) + parameters + mandatory + either prompts or prompt-content + prompts should be a markdown file + prompts used to be a directory but that's deprecated + optional + user, platform, host-dir - create default facts for template rendering] + parameters - external params bound at rendering time + + returns a :schema/prompts-file" [{:keys [parameters prompts user platform host-dir prompt-content] :as opts}] (let [{:keys [metadata] :as prompt-data} (cond @@ -184,16 +195,29 @@ parameters (-> metadata :parameter-values)) renderer (if (= "django" (:prompt-format metadata)) - (partial selmer-render (facts m user platform host-dir)) - (partial moustache-render prompts (facts m user platform host-dir)))] + (fn [arguments message] + (selmer-render (merge (facts m user platform host-dir) arguments) message)) + (fn [arguments message] + (moustache-render prompts (merge (facts m user platform host-dir) arguments) message)))] ((schema/validate :schema/prompts-file) (-> prompt-data - (update :messages #(map renderer %)) + ((fn [m] (assoc m :prompt-function (fn [arguments] + (->> (:messages m) + (map (comp ->message (partial renderer arguments))) + (into [])))))) + (update :messages #(map (partial renderer {}) %)) (update :metadata dissoc :functions :tools :extractors) (assoc :functions (->> (or (:tools metadata) (:functions metadata)) (mapcat function-definition))))))) (comment (get-prompts {:prompts (fs/file "./prompts/examples/curl.md")}) (get-prompts {:prompts (fs/file "./prompts/examples/generate-dockerfile.md")}) - (get-prompts {:prompts (fs/file "./README.md")})) + (get-prompts {:prompts (fs/file "./README.md")}) + (= + ((:prompt-function (get-prompts {:prompts (fs/file "./prompts/examples/qrencode.md")})) {:content "mycontent"}) + [{:role "user", + :content + {:text + "\nGenerate a QR code for the content 'mycontent'.\nSave the generated image to `/thread/resources/qrcode.png`.\nIf the command fails, read the man page and try again.\nIf successful, output the path to the generated image in markdown syntax.", + :type "text"}}]))