diff --git a/prompts/examples/curl.md b/prompts/examples/curl.md index d2c462d..e8ee26e 100644 --- a/prompts/examples/curl.md +++ b/prompts/examples/curl.md @@ -1,4 +1,5 @@ --- +model: claude-3-5-sonnet-20241022 tools: - name: curl --- diff --git a/prompts/examples/explain_dockerfile.md b/prompts/examples/explain_dockerfile.md index f52aa78..eba304b 100644 --- a/prompts/examples/explain_dockerfile.md +++ b/prompts/examples/explain_dockerfile.md @@ -7,6 +7,7 @@ description: | Synonyms: explain my Dockerfile, annotate this Dockerfile... This tool can explain a pre-provided Dockerfile but it can also fetch the Dockerfile from the user's workspace. +model: claude-3-5-sonnet-20241022 tools: - name: cat_file description: fetch a file diff --git a/prompts/examples/hello_world.md b/prompts/examples/hello_world.md index 8f208f4..acba00f 100644 --- a/prompts/examples/hello_world.md +++ b/prompts/examples/hello_world.md @@ -1,6 +1,7 @@ --- name: hello-docker description: run the hello-docker +model: claude-3-5-sonnet-20241022 tools: - name: hello-docker description: print a secret message diff --git a/prompts/examples/mcp-sqlite.md b/prompts/examples/mcp-sqlite.md index ec995a2..b573cdf 100644 --- a/prompts/examples/mcp-sqlite.md +++ b/prompts/examples/mcp-sqlite.md @@ -1,24 +1,70 @@ --- description: | A prompt to seed the database with initial data and demonstrate what you can do with an SQLite MCP Server + Claude +model: claude-3-5-sonnet-20241022 tools: - name: read-query description: Execute a SELECT query on the SQLite database - container: + parameters: &query + type: object + properties: + query: + type: string + description: SELECT SQL query to execute + container: &sqlite image: vonwig/sqlite:latest command: - - "{{database}}" - - "{{sql}}" + - "/mcp/test1.db" + - "{{query|safe}}" + mounts: &mounts + - "mcp-test:/mcp" - name: write-query description: Execute an INSERT, UPDATE, or DELETE query on the SQLite database + parameters: *query + container: *sqlite - name: create-table description: Create a new table in the SQLite database + parameters: *query + container: *sqlite - name: list-tables description: List all tables in the SQLite database + container: + image: vonwig/sqlite:latest + command: + - "/mcp/test1.db" + - "SELECT name from sqlite_master WHERE type='table'" + mounts: *mounts - name: describe-table description: Get the schema information for a specific table + parameters: + type: object + properties: + table_name: + type: string + description: Name of the table to describe + container: + image: vonwig/sqlite:latest + command: + - "/mcp/test1.db" + - "PRAGMA table_info({{table_name}})" + mounts: *mounts - name: append-insight description: Add a business insight to the memo + parameters: + type: object + properties: + insight: + type: string + description: Business insight discovered from data analysis + container: + image: vonwig/bash_alpine + command: + - "-c" + - "echo '{{insight|safe}}' >> /mcp/insights.txt" + mounts: *mounts +prompt-format: django +parameter-values: + topic: Ocean Conservation --- # prompt user diff --git a/src/claude.clj b/src/claude.clj index a07ae49..2b4dc4b 100644 --- a/src/claude.clj +++ b/src/claude.clj @@ -14,7 +14,7 @@ (try (string/trim (slurp (io/file (or (System/getenv "CLAUDE_API_KEY_LOCATION") (System/getenv "HOME")) ".claude-api-key"))) (catch Throwable _ - (throw (ex-info "Unable to read claude-api-key secret" {}))))) + (throw (ex-info "Unable to read claude api-key secret" {}))))) (defn parse-sse [s] (when (string/starts-with? s "data:") @@ -52,7 +52,7 @@ (if tool_calls {:role (:role message) :content (concat - (when (:content message) [{:type "text" :text (:content message)}]) + (when (and (:content message) (not (= "" (:content message)))) [{:type "text" :text (:content message)}]) (->> tool_calls (map (fn [{:keys [id function]}] {:type "tool_use" diff --git a/src/graph.clj b/src/graph.clj index 63abb70..d8a5b68 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) ; TODO should we just select relevant keys instead of removing bad ones + (dissoc metadata :agent :host-dir :workdir :prompt-format :description :name :parameter-values) ; 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/prompts.clj b/src/prompts.clj index 54700c2..a2b9af8 100644 --- a/src/prompts.clj +++ b/src/prompts.clj @@ -132,11 +132,11 @@ (fn [content] (if prompts-file (stache/render-string - content - m - {:partials (partials/file-partials - [(if (fs/directory? prompts-file) prompts-file (fs/parent prompts-file))] - ".md")}) + content + m + {:partials (partials/file-partials + [(if (fs/directory? prompts-file) prompts-file (fs/parent prompts-file))] + ".md")}) (stache/render-string content m))))) (defn selmer-render [m message] @@ -145,9 +145,9 @@ (fn [content] (selmer/render content m)))) -(selmer/add-tag! :tip (fn [args context-map] - (format - "At the very end of the response, add this sentence: \"ℹ️ You can also ask: '%s'\", in the language used by the user, with the question in italic." (first args)))) +(selmer/add-tag! :tip (fn [args context-map] + (format + "At the very end of the response, add this sentence: \"ℹ️ You can also ask: '%s'\", in the language used by the user, with the question in italic." (first args)))) (comment (stache/render-string "yo {{a.0.content}}" {:a [{:content "blah"}]})) @@ -159,7 +159,7 @@ returns map of messages, functions, metadata and optionally error" [{:keys [parameters prompts user platform host-dir prompt-content] :as opts}] (let [{:keys [metadata] :as prompt-data} - (cond + (cond ;; prompt content is already in opts prompt-content (markdown-parser/parse-prompts prompt-content) @@ -179,8 +179,11 @@ :else (markdown-parser/parse-prompts (slurp prompts))) - m (merge (run-extractors (:extractors metadata) opts) parameters) - renderer (if (= "django" (:prompt-format metadata)) + m (merge + (run-extractors (:extractors metadata) opts) + 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)))] ((schema/validate :schema/prompts-file) diff --git a/src/tools.clj b/src/tools.clj index 741c71f..6461a23 100644 --- a/src/tools.clj +++ b/src/tools.clj @@ -90,8 +90,10 @@ false (not= 0 exit-code))] (cond - (and (= :exited done) (not exit-code-fail?)) + (and (= :exited done) (not exit-code-fail?) pty-output (not (= "" pty-output))) (resolve pty-output) + (and (= :exited done) (not exit-code-fail?)) + (resolve "success") (and (= :exited done) exit-code-fail?) (fail (format "call exited with non-zero code (%d): %s" exit-code pty-output)) (= :timeout done)