ZuperAI-14B / toolkit /agent.py
Tyesee's picture
toolkit: agent.py
197725f verified
Raw History Blame Contribute Delete
7.17 kB
# -*- coding: utf-8 -*-
"""ZuperAI — кодинг-агент: по ТЗ генерирует целое решение + тесты → гоняет → чинит.
Отличие от verify.py: ориентирован на ЦЕЛЫЙ модуль/приложение (не одну функцию).
Пишет решение из нескольких функций/классов, сам пишет unittest-тесты, гоняет
их в песочнице и доводит до зелёного. Это «агент, который делает работу», а не
«генератор куска кода».
Запуск:
venv/Scripts/python.exe agent.py "Напиши модуль работы с CSV: чтение, фильтр, агрегация"
venv/Scripts/python.exe agent.py "FastAPI-приложение для заметок с SQLite"
Ключи:
--code deepseek|lmstudio|local кто пишет код (по умолчанию deepseek, если ключ есть)
--judge deepseek|lmstudio|local кто пишет тесты/судит (по умолчанию deepseek)
--attempts N число попыток (по умолчанию 5)
--keep не удалять песочницу
"""
import os
import sys
import tempfile
from webui import (deepseek_available, deepseek_generate,
lmstudio_available, lmstudio_generate)
from verify_util import (TEST_SYSTEM, VERIFY_ATTEMPTS, VERIFY_TIMEOUT,
run_tests, looks_like_tests, tail, ensure_unittest_header)
SYSTEM_AGENT = ("Ты — senior-разработчик. По описанию напиши ПОЛНОЕ решение: "
"весь нужный код в одном файле (solution.py) — функции, классы, "
"обработка ошибок, понятные русские комментарии и докстринги. "
"Не используй внешние зависимости сверх необходимого минимума. "
"Без пояснений вне кода и без ```.")
def _pick(flag, default, available):
if flag not in ("deepseek", "lmstudio", "local"):
return default
if flag == "deepseek" and not available[0]:
return default
if flag == "lmstudio" and not available[1]:
return default
return flag
def _gen(backend, msgs, max_tokens):
if backend == "deepseek":
from webui import _strip_fences
return _strip_fences(deepseek_generate(msgs, max_tokens=max_tokens))
if backend == "lmstudio":
from webui import _strip_fences
return _strip_fences(lmstudio_generate(msgs, max_tokens=max_tokens))
# local: transformers + LoRA 3B
from infer_smart import load, gen, _strip_fences as _sf
if not hasattr(_gen, "_model"):
_gen._model = load()
tok, model = _gen._model
return _sf(gen(tok, model, msgs, max_tokens=max_tokens))
def main():
args = sys.argv[1:]
code_backend, judge = "deepseek", "deepseek"
attempts, keep = VERIFY_ATTEMPTS, False
parts = []
i = 0
while i < len(args):
a = args[i]
if a == "--code" and i + 1 < len(args):
code_backend = args[i + 1]; i += 2; continue
if a == "--judge" and i + 1 < len(args):
judge = args[i + 1]; i += 2; continue
if a == "--attempts" and i + 1 < len(args):
attempts = int(args[i + 1]); i += 2; continue
if a == "--keep":
keep = True; i += 1; continue
parts.append(a); i += 1
prompt = " ".join(parts)
if not prompt:
prompt = "Модуль работы с CSV: чтение файла, фильтрация строк, агрегация по колонке"
avail = (deepseek_available(), lmstudio_available())
code_backend = _pick(code_backend, "deepseek" if avail[0] else "local", avail)
judge = _pick(judge, "deepseek" if avail[0] else "local", avail)
sandbox = tempfile.mkdtemp(prefix="zuperai_agent_")
print(f"[*] код: {code_backend}, судья: {judge}")
# 1) судья пишет тесты по ТЗ
t_msgs = [{"role": "system", "content": TEST_SYSTEM},
{"role": "user",
"content": f"Задача: {prompt}\n\nНапиши unittest-тесты, которые "
f"импортируют решение из solution и проверяют корректность "
f"всех заявленных возможностей. Верни ТОЛЬКО код тестов."}]
tests = ensure_unittest_header(_gen(judge, t_msgs, 1536))
# 2) код-агент пишет решение, гоняем, чиним
msgs = [{"role": "system", "content": SYSTEM_AGENT},
{"role": "user",
"content": f"Задача: {prompt}\n\nТесты:\n{tests}\n\n"
f"Напиши полное решение (solution.py), проходящее эти тесты."}]
code = ""
for attempt in range(1, attempts + 1):
if not code:
code = _gen(code_backend, msgs, 1536)
print(f"[попытка {attempt}/{attempts}] генерирую решение…", file=sys.stderr)
else:
print(f"[попытка {attempt}/{attempts}] тестирую исправленное…", file=sys.stderr)
ok, out = run_tests(code, tests, sandbox, VERIFY_TIMEOUT)
if ok:
print(code)
print(f"\n[OK] тесты прошли, попытка {attempt}")
print("----- тесты -----")
print(tests)
if keep:
print("песочница:", sandbox)
return
print(f"[попытка {attempt}] упало:\n{tail(out)}", file=sys.stderr)
msgs.append({"role": "assistant", "content": code})
msgs.append({"role": "user",
"content": f"Решение не прошло тесты:\n{tail(out)}\n\n"
f"Определи, где ошибка (в решении или в тесте), и верни "
f"исправленный КОД либо исправленный ТЕСТ — одно из двух."})
fix = _gen(judge, msgs, 1536)
if looks_like_tests(fix):
tests = ensure_unittest_header(fix)
code = ""
msgs = msgs[:2]
msgs[1] = {"role": "user",
"content": f"Задача: {prompt}\n\nТесты:\n{tests}\n\n"
f"Напиши полное решение (solution.py), проходящее тесты."}
else:
code = fix
msgs[-2] = {"role": "assistant", "content": code}
print(code)
print(f"\n[!] за {attempts} попыток не прошло — выше последний вариант")
print("----- тесты -----")
print(tests)
if keep:
print("песочница:", sandbox)
if __name__ == "__main__":
main()