summaryrefslogtreecommitdiff
path: root/Omni/Agent/Engine.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-30 21:30:00 -0500
committerBen Sima <ben@bensima.com>2025-11-30 21:30:00 -0500
commit9fa7697cd979eaa15a2479819463c3bdd86cc99a (patch)
tree0eee4aebe8f99608e1ff3f831797dd0214fe4ed0 /Omni/Agent/Engine.hs
parent194173619e0e1940284f4d4fa3de49f5197636c1 (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.hs39
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