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/Telegram.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/Telegram.hs')
| -rw-r--r-- | Omni/Agent/Telegram.hs | 54 |
1 files changed, 51 insertions, 3 deletions
diff --git a/Omni/Agent/Telegram.hs b/Omni/Agent/Telegram.hs index 7b2beaa..7183592 100644 --- a/Omni/Agent/Telegram.hs +++ b/Omni/Agent/Telegram.hs @@ -76,6 +76,7 @@ import Data.Aeson ((.=)) import qualified Data.Aeson as Aeson import qualified Data.Aeson.KeyMap as KeyMap import qualified Data.ByteString.Lazy as BL +import Data.IORef (newIORef, readIORef, writeIORef) import qualified Data.Text as Text import qualified Data.Text.Encoding as TE import Data.Time (getCurrentTime, utcToLocalTime) @@ -110,6 +111,7 @@ import qualified Omni.Agent.Tools.Python as Python import qualified Omni.Agent.Tools.Todos as Todos import qualified Omni.Agent.Tools.WebReader as WebReader import qualified Omni.Agent.Tools.WebSearch as WebSearch +import qualified Omni.Ava.Trace as Trace import qualified Omni.Test as Test import System.Environment (lookupEnv) import Text.Printf (printf) @@ -524,6 +526,14 @@ leaveChat cfg chatId = do runTelegramBot :: Types.TelegramConfig -> Provider.Provider -> IO () runTelegramBot tgConfig provider = do putText "Starting Telegram bot..." + + cleanedCount <- Memory.withMemoryDb Trace.cleanupOldTraces + when (cleanedCount > 0) + <| putText + <| "Cleaned up " + <> tshow cleanedCount + <> " old tool traces" + offsetVar <- newTVarIO 0 botUsername <- getBotUsername tgConfig @@ -551,7 +561,23 @@ runTelegramBot tgConfig provider = do Engine.engineOnToolResult = \toolName success result -> putText <| "Tool result: " <> toolName <> " " <> (if success then "ok" else "err") <> " " <> Text.take 200 result, Engine.engineOnActivity = \activity -> - putText <| "Agent: " <> activity + putText <| "Agent: " <> activity, + Engine.engineOnToolTrace = \toolName input output durationMs -> do + now <- getCurrentTime + let truncatedOutput = Text.take 1000000 output + traceRecord = + Trace.TraceRecord + { Trace.trcId = "", + Trace.trcCreatedAt = tshow now, + Trace.trcToolName = toolName, + Trace.trcInput = input, + Trace.trcOutput = truncatedOutput, + Trace.trcDurationMs = durationMs, + Trace.trcUserId = Nothing, + Trace.trcChatId = Nothing + } + tid <- Memory.withMemoryDb <| \conn -> Trace.insertTrace conn traceRecord + pure (Just tid) } let processBatch = handleMessageBatch tgConfig provider engineCfg botName @@ -1061,6 +1087,17 @@ processEngagedMessage :: processEngagedMessage tgConfig provider engineCfg msg uid userName chatId userMessage conversationContext = do let isGroup = Types.isGroupChat msg + lastTraceIdRef <- newIORef (Nothing :: Maybe Text) + let engineCfgWithTrace = + engineCfg + { Engine.engineOnToolTrace = \toolName input output durationMs -> do + maybeTid <- Engine.engineOnToolTrace engineCfg toolName input output durationMs + case maybeTid of + Just tid -> writeIORef lastTraceIdRef (Just tid) + Nothing -> pure () + pure maybeTid + } + personalMemories <- Memory.recallMemories uid userMessage 5 groupMemories <- if isGroup @@ -1245,7 +1282,11 @@ processEngagedMessage tgConfig provider engineCfg msg uid userName chatId userMe result <- withTypingIndicator tgConfig chatId - <| Engine.runAgentWithProvider engineCfg provider agentCfg userMessage + <| Engine.runAgentWithProvider engineCfgWithTrace provider agentCfg userMessage + + lastTraceId <- readIORef lastTraceIdRef + maybeWebUrl <- lookupEnv "AVA_WEB_URL" + let traceLink = formatTraceLink lastTraceId (Text.pack </ maybeWebUrl) case result of Left err -> do @@ -1253,7 +1294,8 @@ processEngagedMessage tgConfig provider engineCfg msg uid userName chatId userMe _ <- Messages.enqueueImmediate (Just uid) chatId (Types.tmThreadId msg) "sorry, i hit an error. please try again." (Just "agent_error") Nothing pure () Right agentResult -> do - let response = Engine.resultFinalMessage agentResult + let baseResponse = Engine.resultFinalMessage agentResult + response = baseResponse <> traceLink threadId = Types.tmThreadId msg putText <| "Response text: " <> Text.take 200 response @@ -1367,6 +1409,12 @@ mergeTooShort (x : y : rest) | Text.length x < 100 = mergeTooShort ((x <> "\n\n" <> y) : rest) | otherwise = x : mergeTooShort (y : rest) +formatTraceLink :: Maybe Text -> Maybe Text -> Text +formatTraceLink Nothing _ = "" +formatTraceLink _ Nothing = "" +formatTraceLink (Just tid) (Just baseUrl) = + "\n\n[view trace](" <> baseUrl <> "/trace/" <> tid <> ")" + enqueueMultipart :: Maybe Text -> Int -> Maybe Int -> [Text] -> Maybe Text -> IO () enqueueMultipart _ _ _ [] _ = pure () enqueueMultipart mUid chatId mThreadId parts msgType = do |
