diff options
| author | Ben Sima <ben@bensima.com> | 2025-12-19 21:54:54 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-12-19 21:54:54 -0500 |
| commit | fcb8629182fa1552e4a840ccd4ec0aa2b8042cc0 (patch) | |
| tree | e389479cf9349fbdab107da739bceef11cf8e7ee /Omni/Agent/Engine.hs | |
| parent | e856c766584ed933bed0b79c7ef47b6d98b0fb7e (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.hs | 26 |
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 |
