diff options
Diffstat (limited to 'Omni/Agent')
| -rw-r--r-- | Omni/Agent/Engine.hs | 19 | ||||
| -rw-r--r-- | Omni/Agent/Tools.hs | 57 |
2 files changed, 59 insertions, 17 deletions
diff --git a/Omni/Agent/Engine.hs b/Omni/Agent/Engine.hs index 0dc7c50..343ccc3 100644 --- a/Omni/Agent/Engine.hs +++ b/Omni/Agent/Engine.hs @@ -799,8 +799,9 @@ executeToolCallsWithTracking engineCfg toolMap tcs initialTestFailures initialEd resultValue <- toolExecute tool args 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) + rawResultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue)) + resultText = truncateToolResult rawResultText + isTestCall = name == "run_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 @@ -830,6 +831,20 @@ executeToolCallsWithTracking engineCfg toolMap tcs initialTestFailures initialEd _ -> False isOldStrNotFoundError _ = False +-- | Maximum characters for any tool result (engine-level safety net) +maxToolResultChars :: Int +maxToolResultChars = 10000 + +-- | Truncate tool result if too long (engine-level safety net) +truncateToolResult :: Text -> Text +truncateToolResult t + | Text.length t <= maxToolResultChars = t + | otherwise = + Text.take maxToolResultChars t + <> "\n\n[TOOL RESULT TRUNCATED by engine - " + <> tshow (Text.length t - maxToolResultChars) + <> " chars omitted]" + -- | Estimate cost in cents from token count. -- Uses blended input/output rates (roughly 2:1 output:input ratio). -- Prices as of Dec 2024 from OpenRouter. diff --git a/Omni/Agent/Tools.hs b/Omni/Agent/Tools.hs index 0bd394f..5078b11 100644 --- a/Omni/Agent/Tools.hs +++ b/Omni/Agent/Tools.hs @@ -214,6 +214,14 @@ instance Aeson.FromJSON ToolResult where maxOutputChars :: Int maxOutputChars = 8000 +-- | Maximum lines per read_file call +maxReadLinesPerCall :: Int +maxReadLinesPerCall = 400 + +-- | Files larger than this require explicit line ranges +largeFileLineThreshold :: Int +largeFileLineThreshold = 500 + -- | Truncate output if too long, adding a notice truncateOutput :: Text -> Text truncateOutput output @@ -296,12 +304,38 @@ executeReadFile v = then do content <- TextIO.readFile path let allLines = Text.lines content - startIdx = maybe 0 (\n -> n - 1) (readFileStartLine args) - endIdx = maybe (length allLines) identity (readFileEndLine args) - selectedLines = take (endIdx - startIdx) (drop startIdx allLines) - numberedLines = zipWith formatLine [(startIdx + 1) ..] selectedLines - result = Text.unlines numberedLines - pure <| mkSuccess result + totalLines = length allLines + hasRange = isJust (readFileStartLine args) || isJust (readFileEndLine args) + + if not hasRange && totalLines > largeFileLineThreshold + then + pure + <| mkError + ( "File is large (" + <> tshow totalLines + <> " lines). You must provide start_line and end_line, with at most " + <> tshow maxReadLinesPerCall + <> " lines per call." + ) + else do + let startIdx = maybe 0 (\n -> n - 1) (readFileStartLine args) + endIdx = maybe (length allLines) identity (readFileEndLine args) + requestedLines = endIdx - startIdx + if requestedLines > maxReadLinesPerCall + then + pure + <| mkError + ( "Requested range is too large (" + <> tshow requestedLines + <> " lines). Max " + <> tshow maxReadLinesPerCall + <> " lines per read_file call." + ) + else do + let selectedLines = take requestedLines (drop startIdx allLines) + numberedLines = zipWith formatLine [(startIdx + 1) ..] selectedLines + result = Text.unlines numberedLines + pure <| mkSuccess result else pure <| mkError ("File not found: " <> readFilePath args) where formatLine :: Int -> Text -> Text @@ -520,20 +554,13 @@ executeRunBash v = (exitCode, stdoutStr, stderrStr) <- Process.readCreateProcessWithExitCode proc "" let output = Text.pack stdoutStr <> Text.pack stderrStr case exitCode of - Exit.ExitSuccess -> - pure - <| Aeson.toJSON - <| ToolResult - { toolResultSuccess = True, - toolResultOutput = output, - toolResultError = Nothing - } + Exit.ExitSuccess -> pure <| mkSuccess output Exit.ExitFailure code -> pure <| Aeson.toJSON <| ToolResult { toolResultSuccess = False, - toolResultOutput = output, + toolResultOutput = truncateOutput output, toolResultError = Just ("Exit code: " <> tshow code) } |
