diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-30 21:30:00 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-30 21:30:00 -0500 |
| commit | 9fa7697cd979eaa15a2479819463c3bdd86cc99a (patch) | |
| tree | 0eee4aebe8f99608e1ff3f831797dd0214fe4ed0 /Omni/Agent/Engine.hs | |
| parent | 194173619e0e1940284f4d4fa3de49f5197636c1 (diff) | |
Add agent observability: event logging and storage
- Add Omni/Agent/Event.hs with AgentEvent types
- Add agent_events table schema and CRUD functions to Core.hs
- Add new callbacks to Engine.hs: onAssistant, onToolResult, onComplete, onError
- Wire event logging into Worker.hs with session tracking
Events are now persisted to SQLite for each agent work session,
enabling visibility into agent reasoning and tool usage.
Task-Id: t-197.1
Task-Id: t-197.2
Task-Id: t-197.3
Diffstat (limited to 'Omni/Agent/Engine.hs')
| -rw-r--r-- | Omni/Agent/Engine.hs | 39 |
1 files changed, 26 insertions, 13 deletions
diff --git a/Omni/Agent/Engine.hs b/Omni/Agent/Engine.hs index e019341..1f5dcc8 100644 --- a/Omni/Agent/Engine.hs +++ b/Omni/Agent/Engine.hs @@ -254,7 +254,11 @@ data EngineConfig = EngineConfig { engineLLM :: LLM, engineOnCost :: Int -> Int -> IO (), engineOnActivity :: Text -> IO (), - engineOnToolCall :: Text -> Text -> IO () + engineOnToolCall :: Text -> Text -> IO (), + engineOnAssistant :: Text -> IO (), + engineOnToolResult :: Text -> Bool -> Text -> IO (), + engineOnComplete :: IO (), + engineOnError :: Text -> IO () } defaultEngineConfig :: EngineConfig @@ -263,7 +267,11 @@ defaultEngineConfig = { engineLLM = defaultLLM, engineOnCost = \_ _ -> pure (), engineOnActivity = \_ -> pure (), - engineOnToolCall = \_ _ -> pure () + engineOnToolCall = \_ _ -> pure (), + engineOnAssistant = \_ -> pure (), + engineOnToolResult = \_ _ _ -> pure (), + engineOnComplete = pure (), + engineOnError = \_ -> pure () } data AgentResult = AgentResult @@ -495,26 +503,30 @@ runAgent engineCfg agentCfg userPrompt = do loop :: LLM -> [Tool] -> Map.Map Text Tool -> [Message] -> Int -> Int -> Int -> IO (Either Text AgentResult) loop llm tools' toolMap msgs iteration totalCalls totalTokens - | iteration >= maxIter = - pure - <| Left - <| "Max iterations (" - <> tshow maxIter - <> ") reached" + | iteration >= maxIter = do + let errMsg = "Max iterations (" <> tshow maxIter <> ") reached" + engineOnError engineCfg errMsg + pure <| Left errMsg | otherwise = do engineOnActivity engineCfg <| "Iteration " <> tshow (iteration + 1) result <- chatWithUsage llm tools' msgs case result of - Left err -> pure (Left err) + Left err -> do + engineOnError engineCfg err + pure (Left err) Right chatRes -> do let msg = chatMessage chatRes tokens = maybe 0 usageTotalTokens (chatUsage chatRes) cost = estimateCost (llmModel llm) tokens engineOnCost engineCfg tokens cost let newTokens = totalTokens + tokens + let assistantText = msgContent msg + unless (Text.null assistantText) <| + engineOnAssistant engineCfg assistantText case msgToolCalls msg of Nothing -> do engineOnActivity engineCfg "Agent completed" + engineOnComplete engineCfg pure <| Right <| AgentResult @@ -526,6 +538,7 @@ runAgent engineCfg agentCfg userPrompt = do } Just [] -> do engineOnActivity engineCfg "Agent completed (empty tool calls)" + engineOnComplete engineCfg pure <| Right <| AgentResult @@ -552,22 +565,22 @@ executeToolCalls engineCfg toolMap = traverse executeSingle argsText = fcArguments (tcFunction tc) callId = tcId tc engineOnActivity engineCfg <| "Executing tool: " <> name + engineOnToolCall engineCfg name argsText case Map.lookup name toolMap of Nothing -> do let errMsg = "Tool not found: " <> name - engineOnToolCall engineCfg name errMsg + engineOnToolResult engineCfg name False errMsg pure <| Message ToolRole errMsg Nothing (Just callId) Just tool -> do case Aeson.decode (BL.fromStrict (TE.encodeUtf8 argsText)) of Nothing -> do let errMsg = "Invalid JSON arguments: " <> argsText - engineOnToolCall engineCfg name errMsg + engineOnToolResult engineCfg name False errMsg pure <| Message ToolRole errMsg Nothing (Just callId) Just args -> do resultValue <- toolExecute tool args let resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue)) - summary = Text.take 100 resultText - engineOnToolCall engineCfg name summary + engineOnToolResult engineCfg name True resultText pure <| Message ToolRole resultText Nothing (Just callId) estimateCost :: Text -> Int -> Int |
