File size: 7,168 Bytes
197725f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# -*- 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()