diff options
| author | Ben Sima <ben@bensima.com> | 2025-12-19 16:09:20 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-12-19 16:09:20 -0500 |
| commit | 37d6503342ef9e171fef88960f7baceaa4d1a641 (patch) | |
| tree | e8e4c93f6dc5bd982e1fa6b3a2c88125844b2669 /Omni/Agent/Prompts | |
| parent | 826f97707da555b60e210182859ec83d995b9817 (diff) | |
Add prompt templating system with mustache
- Add promptsDir to Paths.hs for $AVA_DATA_ROOT/prompts/
- Create Omni.Agent.Prompts module with:
- Mustache template loading and rendering
- Automatic partial resolution via automaticCompile
- Frontmatter/metadata parsing for list command
- Create omni-agent-prompt CLI for previewing prompts:
- list: show all available prompts
- render: render prompt with --var and --json context
- Prompts use .mustache extension for automaticCompile compatibility
- Partials referenced with full extension: {{> shared/memory.mustache}}
Diffstat (limited to 'Omni/Agent/Prompts')
| -rw-r--r-- | Omni/Agent/Prompts/Cli.hs | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/Omni/Agent/Prompts/Cli.hs b/Omni/Agent/Prompts/Cli.hs new file mode 100644 index 0000000..c0a792f --- /dev/null +++ b/Omni/Agent/Prompts/Cli.hs @@ -0,0 +1,110 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- | CLI tool for previewing and debugging prompt templates. +-- +-- Usage: +-- omni-agent-prompt list +-- omni-agent-prompt agents/telegram/system +-- omni-agent-prompt subagents/coder/system --var namespace=Omni/Jr --var task="Add feature" +-- omni-agent-prompt subagents/generic/system --json '{"task":"Research X","role_description":"research"}' +-- +-- : out omni-agent-prompt +-- : dep aeson +-- : dep bytestring +-- : dep text +-- : dep mustache +-- : dep unordered-containers +module Omni.Agent.Prompts.Cli (main) where + +import Alpha +import qualified Data.Aeson as Aeson +import qualified Data.Aeson.Key as Key +import qualified Data.Aeson.KeyMap as KeyMap +import qualified Data.ByteString.Lazy as BL +import qualified Data.Text as Text +import qualified Data.Text.Encoding as TE +import qualified Data.Text.IO as TextIO +import qualified Omni.Agent.Prompts as Prompts +import qualified System.Environment as Env +import qualified System.Exit as Exit + +main :: IO () +main = do + args <- Env.getArgs + case args of + [] -> usage + ["--help"] -> usage + ["-h"] -> usage + ["list"] -> listPrompts + (pid : rest) -> renderPromptCmd (Text.pack pid) rest + +usage :: IO () +usage = do + putText "omni-agent-prompt - Preview and debug prompt templates" + putText "" + putText "Usage:" + putText " omni-agent-prompt list" + putText " omni-agent-prompt <prompt-id> [--var key=value]... [--json '{...}']" + putText "" + putText "Examples:" + putText " omni-agent-prompt agents/telegram/system" + putText " omni-agent-prompt subagents/coder/system --var namespace=Omni/Jr --var task='Add feature'" + putText " omni-agent-prompt subagents/generic/system --json '{\"task\":\"Research X\"}'" + putText "" + putText <| "Prompts directory: " <> Text.pack Prompts.promptsDir + +listPrompts :: IO () +listPrompts = do + prompts <- Prompts.listPrompts + if null prompts + then do + putText "No prompts found." + putText <| "Prompts directory: " <> Text.pack Prompts.promptsDir + else do + putText "Available prompts:" + putText "" + forM_ prompts <| \(pid, meta) -> do + let desc = fromMaybe "" (Prompts.pmDescription meta) + if Text.null desc + then putText <| " " <> pid + else putText <| " " <> pid <> " - " <> desc + +renderPromptCmd :: Text -> [String] -> IO () +renderPromptCmd pid args = do + let (jsonCtx, kvPairs) = parseArgs args + ctx <- case jsonCtx of + Nothing -> pure <| buildContext kvPairs + Just jsonStr -> case Aeson.decode (BL.fromStrict (TE.encodeUtf8 jsonStr)) of + Nothing -> do + putText <| "Error: Invalid JSON: " <> jsonStr + Exit.exitFailure + Just obj -> pure <| mergeContext obj kvPairs + + result <- Prompts.renderPrompt pid ctx + case result of + Left err -> do + putText <| "Error: " <> err + Exit.exitFailure + Right rendered -> TextIO.putStrLn rendered + +parseArgs :: [String] -> (Maybe Text, [(Text, Text)]) +parseArgs = go Nothing [] + where + go json kvs [] = (json, reverse kvs) + go _json kvs ("--json" : val : rest) = go (Just (Text.pack val)) kvs rest + go json kvs ("--var" : kv : rest) = + case Text.breakOn "=" (Text.pack kv) of + (k, v) + | not (Text.null v) -> go json ((k, Text.drop 1 v) : kvs) rest + | otherwise -> go json kvs rest + go json kvs (_ : rest) = go json kvs rest + +buildContext :: [(Text, Text)] -> Aeson.Value +buildContext kvs = + Aeson.Object <| KeyMap.fromList [(Key.fromText k, Aeson.String v) | (k, v) <- kvs] + +mergeContext :: Aeson.Value -> [(Text, Text)] -> Aeson.Value +mergeContext (Aeson.Object obj) kvs = + Aeson.Object <| foldr (\(k, v) m -> KeyMap.insert (Key.fromText k) (Aeson.String v) m) obj kvs +mergeContext _ kvs = buildContext kvs |
