summaryrefslogtreecommitdiff
path: root/Omni/Agent/Engine.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-12-19 21:54:54 -0500
committerBen Sima <ben@bensima.com>2025-12-19 21:54:54 -0500
commitfcb8629182fa1552e4a840ccd4ec0aa2b8042cc0 (patch)
treee389479cf9349fbdab107da739bceef11cf8e7ee /Omni/Agent/Engine.hs
parente856c766584ed933bed0b79c7ef47b6d98b0fb7e (diff)
feat(ava): add tool trace viewer mini-app
- Add SQLite storage for tool traces (Omni/Ava/Trace.hs) - Add web server to serve trace viewer (Omni/Ava/Web.hs) - Add HTML/CSS/JS trace viewer UI (Omni/Ava/Web/trace.html) - Integrate trace storage into Engine.hs tool execution callback - Add trace links to Telegram responses when AVA_WEB_URL is set - Configure Tailscale Funnel for public access - Fix pre-push hook variable scope bug - Add direnv, bash, nix to Ava service PATH - Add mustache dep to Ava.hs for template rendering Epic: t-272
Diffstat (limited to 'Omni/Agent/Engine.hs')
-rw-r--r--Omni/Agent/Engine.hs26
1 files changed, 21 insertions, 5 deletions
diff --git a/Omni/Agent/Engine.hs b/Omni/Agent/Engine.hs
index f137ddb..0dc7c50 100644
--- a/Omni/Agent/Engine.hs
+++ b/Omni/Agent/Engine.hs
@@ -14,6 +14,7 @@
-- : dep http-conduit
-- : dep aeson
-- : dep case-insensitive
+-- : dep time
module Omni.Agent.Engine
( Tool (..),
LLM (..),
@@ -56,6 +57,7 @@ import Data.IORef (newIORef, writeIORef)
import qualified Data.Map.Strict as Map
import qualified Data.Text as Text
import qualified Data.Text.Encoding as TE
+import qualified Data.Time as Time
import qualified Network.HTTP.Simple as HTTP
import qualified Omni.Agent.Provider as Provider
import qualified Omni.Test as Test
@@ -378,7 +380,8 @@ data EngineConfig = EngineConfig
engineOnToolResult :: Text -> Bool -> Text -> IO (),
engineOnComplete :: IO (),
engineOnError :: Text -> IO (),
- engineOnGuardrail :: GuardrailResult -> IO ()
+ engineOnGuardrail :: GuardrailResult -> IO (),
+ engineOnToolTrace :: Text -> Text -> Text -> Int -> IO (Maybe Text)
}
defaultEngineConfig :: EngineConfig
@@ -392,7 +395,8 @@ defaultEngineConfig =
engineOnToolResult = \_ _ _ -> pure (),
engineOnComplete = pure (),
engineOnError = \_ -> pure (),
- engineOnGuardrail = \_ -> pure ()
+ engineOnGuardrail = \_ -> pure (),
+ engineOnToolTrace = \_ _ _ _ -> pure Nothing
}
data AgentResult = AgentResult
@@ -791,14 +795,18 @@ executeToolCallsWithTracking engineCfg toolMap tcs initialTestFailures initialEd
engineOnToolResult engineCfg name False errMsg
pure (Message ToolRole errMsg Nothing (Just callId), 0, 0)
Just args -> do
+ startTime <- Time.getCurrentTime
resultValue <- toolExecute tool args
- let resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue))
+ endTime <- Time.getCurrentTime
+ let durationMs = round (Time.diffUTCTime endTime startTime * 1000)
+ resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue))
isTestCall = name == "bash" && ("bild --test" `Text.isInfixOf` argsText || "bild -t" `Text.isInfixOf` argsText)
isTestFailure = isTestCall && isFailureResult resultValue
testDelta = if isTestFailure then 1 else 0
isEditFailure = name == "edit_file" && isOldStrNotFoundError resultValue
editDelta = if isEditFailure then 1 else 0
engineOnToolResult engineCfg name True resultText
+ _ <- engineOnToolTrace engineCfg name argsText resultText durationMs
pure (Message ToolRole resultText Nothing (Just callId), testDelta, editDelta)
isFailureResult :: Aeson.Value -> Bool
@@ -976,14 +984,18 @@ runAgentWithProvider engineCfg provider agentCfg userPrompt = do
engineOnToolResult eCfg name False errMsg
pure (Provider.Message Provider.ToolRole errMsg Nothing (Just callId), 0, 0)
Just args -> do
+ startTime <- Time.getCurrentTime
resultValue <- toolExecute tool args
- let resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue))
+ endTime <- Time.getCurrentTime
+ let durationMs = round (Time.diffUTCTime endTime startTime * 1000)
+ resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue))
isTestCall = name == "bash" && ("bild --test" `Text.isInfixOf` argsText || "bild -t" `Text.isInfixOf` argsText)
isTestFailure = isTestCall && isFailureResultProvider resultValue
testDelta = if isTestFailure then 1 else 0
isEditFailure = name == "edit_file" && isOldStrNotFoundProvider resultValue
editDelta = if isEditFailure then 1 else 0
engineOnToolResult eCfg name True resultText
+ _ <- engineOnToolTrace eCfg name argsText resultText durationMs
pure (Provider.Message Provider.ToolRole resultText Nothing (Just callId), testDelta, editDelta)
isFailureResultProvider :: Aeson.Value -> Bool
@@ -1157,14 +1169,18 @@ runAgentWithProviderStreaming engineCfg provider agentCfg userPrompt onStreamChu
engineOnToolResult eCfg name False errMsg
pure (Provider.Message Provider.ToolRole errMsg Nothing (Just callId), 0, 0)
Just args -> do
+ startTime <- Time.getCurrentTime
resultValue <- toolExecute tool args
- let resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue))
+ endTime <- Time.getCurrentTime
+ let durationMs = round (Time.diffUTCTime endTime startTime * 1000)
+ resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue))
isTestCall = name == "bash" && ("bild --test" `Text.isInfixOf` argsText || "bild -t" `Text.isInfixOf` argsText)
isTestFailure = isTestCall && isFailureResultStreaming resultValue
testDelta = if isTestFailure then 1 else 0
isEditFailure = name == "edit_file" && isOldStrNotFoundStreaming resultValue
editDelta = if isEditFailure then 1 else 0
engineOnToolResult eCfg name True resultText
+ _ <- engineOnToolTrace eCfg name argsText resultText durationMs
pure (Provider.Message Provider.ToolRole resultText Nothing (Just callId), testDelta, editDelta)
isFailureResultStreaming :: Aeson.Value -> Bool