Spaces:
Running on Zero
Running on Zero
File size: 5,879 Bytes
98acb70 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | -- The web adapter only supplies a backend and an isolated filesystem.
-- Parsing, dispatch, approval, recovery, repetition and eviction remain Lua.
local Agent = require('agent')
local tools = require('tools')
local trace = require('trace')
local protocol = require('protocol')
local tasks = require('eval.tasks.tasks').tasks
local M = {}
function M.tasks_json()
local out = {}
for _, t in ipairs(tasks) do
out[#out + 1] = {id=t.id, prompt=t.prompt, tools=t.tools,
budget=t.budget or 4096, approve=t.approve == true, script=t.script}
end
return trace.encode(out)
end
function M.registry(names, files)
local reg = tools.registry()
for _, name in ipairs(names) do
local spec = {}
for k, v in pairs(assert(tools[name], 'unknown tool')) do spec[k] = v end
if name == 'read_file' then
spec.run = function(a)
local body = files[a.path]
if body == nil then error('cannot open ' .. a.path .. ': No such file', 0) end
if #body > tools.MAX_OBSERVATION then
return body:sub(1, tools.MAX_OBSERVATION) .. '\n[truncated]'
end
return body
end
elseif name == 'write_file' then
spec.run = function(a)
-- These keys never become host filesystem paths.
if #a.path > 200 or #a.text > 8192 then error('virtual file too large', 0) end
local count = 0
for _ in pairs(files) do count = count + 1 end
if count >= 32 and files[a.path] == nil then error('virtual file limit reached', 0) end
files[a.path] = a.text
return string.format('wrote %d bytes to %s', #a.text, a.path)
end
elseif name == 'list_dir' then
spec.run = function(a)
local prefix = a.path or '.'
prefix = prefix:gsub('^%./', ''):gsub('/$', '')
if prefix == '.' then prefix = '' end
if prefix ~= '' then prefix = prefix .. '/' end
local names, seen = {}, {}
for path in pairs(files) do
if path:sub(1, #prefix) == prefix then
local name = path:sub(#prefix + 1):match('^[^/]+')
if name and not seen[name] then names[#names+1] = name; seen[name] = true end
end
end
table.sort(names)
return table.concat(names, '\n')
end
elseif name == 'calc' then
local calculate = spec.run
spec.run = function(a)
if #a.expr > 512 then error('expression exceeds 512 characters', 0) end
return calculate(a)
end
end
reg:add(spec)
end
return reg
end
function M.run(config_json, files_json, factory)
local o = assert(trace.decode(config_json))
local files = assert(trace.decode(files_json))
local task
for _, t in ipairs(tasks) do if t.id == o.task_id then task = t end end
local names = o.tool_names or (task and task.tools) or {'calc','read_file','list_dir','write_file'}
local reg = M.registry(names, files)
local backend, tokenizer
if o.model == 'scripted' then
assert(task and o.prompt == task.prompt, 'Scripted replay requires an unchanged preset task.')
local i = 0
backend = {complete=function()
i = i + 1
return task.script[i] or 'CALL: {"tool":"answer","args":{"text":"(script exhausted)"}}', {}
end}
tokenizer = require('bpe').approximate()
else
backend, tokenizer = factory(reg, config_json)
end
local old_render = protocol.render_system
if o.model == 'tiny' then protocol.render_system = protocol.render_compact end
local fatal_on = {}
if o.error_recovery == false or o.parse_recovery == false then fatal_on.parse_error = true end
if o.error_recovery == false or o.tool_recovery == false then
fatal_on.tool_error = true; fatal_on.timeout = true
end
local approvals = {}
local agent = Agent.new({backend=backend, tools=reg, tokenizer=tokenizer,
max_steps=o.max_steps or 10, max_tokens=o.context_limit or 4096,
repeat_threshold=o.loop_detector == false and 10000 or 3,
policy=o.policy or 'elide_observations', fatal_on=fatal_on,
approve=function(call)
local allowed = o.approval_gate == false or o.approve_write == true
approvals[#approvals+1] = {call=call, allowed=allowed, gate_enabled=o.approval_gate ~= false}
return allowed
end})
local answer, reason, steps = agent:run(o.prompt)
protocol.render_system = old_render
local evicted = false
for _, s in ipairs(steps) do if s.evicted then evicted = true end end
local passed, why
if task and o.prompt == task.prompt and not o.decoder_trial then
-- Preserve the original judges, redirecting their out.txt checks to
-- the same in-memory files used by the public tools.
local old_open, old_remove = io.open, os.remove
io.open = function(path)
if files[path] == nil then return nil end
return {read=function() return files[path] end, close=function() end}
end
os.remove = function(path) files[path] = nil; return true end
local judge = o.model == 'scripted' and task.check or task.solved
local ok, a, b = pcall(judge, answer, steps, {evicted=evicted, reason=reason})
io.open, os.remove = old_open, old_remove
if not ok then error(a) end
passed, why = a, b
end
return trace.encode({answer=answer, reason=reason, steps=steps, passed=passed,
judge_note=why, evicted=evicted, approvals=approvals, files=files})
end
return M
|