diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e025d0e..67f3b40 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -3,11 +3,13 @@ ## ⛔ ОТВЕЧАТЬ КРАТКО — АБСОЛЮТНОЕ ПРАВИЛО - Вопрос → короткий ответ → СТОП. Не рассуждать, не исследовать без команды. - НЕ делать ничего "попутно" без явной просьбы. Сделал — стоп — ждёшь команды. +- Без приветствий и лишних рассуждений. Код — только по запросу. Не бормотать. > Подробные правила: [`.github/pravila.md`](pravila.md) 1. Не трогать рабочий код без явного указания. 2. Файлы редактировать локально — `~/remote_dev/` = `~/terra/` на ВМ (sshfs), SCP не нужен. + Перед любым запуском команд проверять, что локальные файлы и содержимое на ВМ синхронизированы; при сомнении ориентироваться на состояние файлов на ВМ. 3. Все команды — **только через SSH**, никогда локально: ```bash ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА' diff --git a/.github/pravila.md b/.github/pravila.md index be15fff..3b42072 100644 --- a/.github/pravila.md +++ b/.github/pravila.md @@ -5,6 +5,8 @@ `~/remote_dev/` (локально) примонтирован через sshfs к `~/terra/` на ВМ — **одна ФС**. Файлы, сохранённые локально, мгновенно видны на ВМ. SCP не нужен. +Перед любым запуском команд сначала убедиться, что локальная копия и содержимое на ВМ синхронизированы; если есть сомнение в рассинхроне, сверяться с файлами именно на ВМ. + Монтирование может слетать. Признак: файлы рассинхронизированы. ```bash diff --git a/LLM/LLM_PROMPT_TRAINING_2026-04-28.md b/LLM/LLM_PROMPT_TRAINING_2026-04-28.md new file mode 100644 index 0000000..6fad3ec --- /dev/null +++ b/LLM/LLM_PROMPT_TRAINING_2026-04-28.md @@ -0,0 +1,237 @@ +# LLM Prompt Training Log + +Дата: 2026-04-28 +Цель: довести универсальный промпт для генерации кода функций до состояния, при котором сгенерированный код проходит синтаксическую проверку и проверку смысла "LLM что делает" на всех языках, с учетом кривых пользовательских условий. + +## Правило цикла +1. Составить человеческую задачу для LLM. +2. Получить сгенерированный код. +3. Прогнать код через линтер / syntax check. +4. Прогнать проверку "LLM что делает". +5. Зафиксировать результат в этом файле. +6. Исправить промпт. +7. Повторить на новых задачах и новых языках. + +## Цели промпта +- Пользователь может давать неточные, неполные или кривые условия. +- LLM должна уточнять или нормализовать задачу внутри ответа. +- LLM должна возвращать код функции, который соответствует реальному runtime-контуру. +- Сгенерированный код должен быть проверяемым линтером. +- Сгенерированный код должен проходить семантическую проверку "LLM что делает". +- Промпт должен работать на всех поддерживаемых языках. + +## Критерии успеха +- Код валиден по синтаксису. +- Код соответствует ожидаемому entrypoint/handler контракту. +- Код не содержит лишнего wrapper-кода, если он не нужен для пользовательского артефакта. +- Ответ LLM объясняет, что делает код. +- Ответ LLM не выдумывает поведение, которого нет в коде. +- На негативных примерах LLM не маскирует ошибки. + +## Языки для прогона +- Node.js +- Python +- PHP +- Ruby +- Go + +## Формат записи по каждому циклу +- Дата и номер итерации. +- Исходный промпт. +- Набор задач. +- Что сгенерировала LLM. +- Результат линтера. +- Результат проверки "LLM что делает". +- Что сломалось. +- Что изменено в промпте. +- Результат следующего прогона. + +## Текущий статус +- Базовая структура журнала создана. +- Следующий шаг: сформировать первый универсальный промпт и прогнать по языкам на множестве задач. + +## Итерация 1 + +### Промпт +- Набор прямых задач на генерацию кода по языкам. +- Требование: код + краткое объяснение. + +### Прогон +- Node.js, Python, PHP, Ruby, Go. + +### Результат +- Все сгенерированные куски кода прошли syntax/lint check. +- Объяснение присутствовало во всех ответах, но формат заголовка плавал: `**Explanation**`, `**Что делает код**`, `**LLM**`, `**LLM:**`. +- Node.js ответ был runtime-safe в синтаксическом смысле, но prompt ещё не фиксировал достаточно жёстко формат блока объяснения. +- PHP/Ruby ответы местами использовали более свободные runtime-объяснения, чем хотелось бы для единого шаблона. + +### Вывод +- Нужен более жёсткий универсальный промпт: точный заголовок блока объяснения, явный запрет на выдуманные SDK/контракты, более тесная привязка к runtime. + +### Что изменено в промпте после итерации 1 +- Зафиксирован точный заголовок `LLM что делает`. +- Добавлены явные ограничения по runtime-контракту. + +## Итерация 8 + +### Промпт +- Строгий формат ответа сохранился. +- Добавлены более разнообразные задачи: greeting и sum numbers. + +### Прогон +- Node.js, Python, PHP, Ruby. + +### Результат +- Все четыре языка стабильно выдавали синтаксически корректный код и проходили lint. +- Node.js держит exact heading `LLM что делает` и на greeting, и на sum numbers. +- Python и Ruby также стабильно соблюдают exact heading и fenced code block. +- PHP на части mixed-case ответов всё ещё иногда отдаёт нестандартный заголовок вроде `LLM greeting handler` или `LLM what does`, хотя код и lint проходят. + +### Вывод +- Prompt уже достаточно силён для кода и линтера на всех поддерживаемых языках. +- Главная остаточная проблема — полная фиксация заголовка explanation в PHP во всех разновидностях задач. +- Для следующей правки стоит ещё сильнее зажать именно PHP‑формат ответа либо принять это как локальную особенность модели. +- Добавлены запреты на выдуманные SDK, фреймворки и wrapper-код. + +## Итерация 2 + +### Промпт +- Универсальный prompt с акцентом на broken user input, runtime contract и точный раздел `LLM что делает`. +- Go исключён из цикла по явному указанию пользователя. + +### Прогон +- Node.js, Python, PHP, Ruby. + +### Результат +- На первом проходе Node.js с более тяжёлой формулировкой давал `HTTP 400 invalid JSON: unexpected EOF` от AI endpoint. +- После упрощения Node.js запроса он стал проходить syntax/lint check. +- Python, PHP, Ruby прошли syntax/lint check. +- Во всех ответах explanation был, но заголовок снова не совпал с точным `LLM что делает`; LLM использовала варианты `**LLM:**`, `**Explanation**` и похожие. + +### Вывод +- Главная оставшаяся проблема — формат блока объяснения. +- Для следующей итерации нужен ещё более жёсткий формат ответа: literal heading без markdown-обёрток и без альтернативных названий. + +### Что изменено в промпте после итерации 2 +- Усилен запрет на альтернативные заголовки блока объяснения. +- Добавлено требование, чтобы первая строка раздела была ровно `LLM что делает`. +- Уточнено, что после заголовка должны идти только короткие пункты. + +## Итерация 3 + +### Промпт +- Универсальный prompt с фиксированным разделом `LLM что делает`. +- Кривые и разнотипные пользовательские задачи: приветствие, сумма чисел, default fallback, missing fields. +- Go уже исключён из цикла. + +### Прогон +- Node.js, Python, PHP, Ruby. + +### Результат +- Python, PHP и Ruby стабильно вернули синтаксически корректный код. +- Node.js дважды отвалился на уровне AI endpoint с `HTTP 400 invalid JSON: unexpected EOF` до получения ответа. +- Для Python/PHP/Ruby explanation присутствовал, но часть ответов оборачивала пояснение в markdown или использовала нестандартную форму заголовка, из-за чего точный парсинг блока `LLM что делает` был нестабилен. +- Линтер везде, где код был получен, показал `✅ Синтаксис корректен.` + +### Вывод +- Prompt уже близок к рабочему, но ещё не дожат по формату блока объяснения. +- Нужна ещё более жёсткая инструкция: explanation без markdown-обёрток, ровно один заголовок, затем только пункты. +- Node.js-case надо формулировать проще, иначе AI endpoint иногда вообще не возвращает JSON-ответ. + +### Что изменено в промпте после итерации 3 +- Уточнено, что раздел `LLM что делает` должен идти без `**`, без code fences и без альтернативных названий. +- Проговаривание структуры ответа стало ещё более жёстким. + +## Итерация 4 + +### Промпт +- Ещё более жёсткая формулировка ответа. +- Короткие и разнообразные задачи: greet, sum numbers, fallback defaults, broken input. +- Go не используется. + +### Прогон +- Node.js, Python, PHP, Ruby. + +### Результат +- Node.js, Python, PHP и Ruby синтаксически сгенерировались, но формат ответа был нестабилен. +- Node.js вернул ответ с заголовками `Normalized task:` / `Code:` / `LLM что делает:` вместо требуемой точной структуры. +- Python ответил без точного заголовка `LLM что делает` и с markdown-упаковкой блока. +- PHP и Ruby в части случаев вообще не отдали код в fenced-блоке, из-за чего extractor не смог вытащить код. +- Где код удалось извлечь, syntax/lint check проходил. + +### Вывод +- Prompt уже умеет удерживать синтаксис, но всё ещё не закрепил формат ответа. +- Следующая правка должна требовать строгую структуру ответа: фиксированный порядок секций, fenced code block и plain-text heading `LLM что делает`. + +### Что изменено в промпте после итерации 4 +- Усилен запрет на альтернативные заголовки и markdown-упаковку explanation. +- Следующий вариант должен требовать точную структуру ответа с fenced code block. + +## Итерация 5 + +### Промпт +- Строгий шаблон ответа: нормализация, fenced code block, `LLM что делает`. +- Набор разнообразных задач: приветствие, default fallback, суммы чисел. +- Go по-прежнему исключён. + +### Прогон +- Node.js, Python, PHP, Ruby. + +### Результат +- Python, PHP и Ruby для части задач вернули синтаксически корректный код и соблюли fenced block + heading. +- Ruby с задачей про сумму чисел выполнил требование почти полностью: точный heading `LLM что делает` присутствовал. +- Python/Php с простыми задачами тоже начали соблюдать заголовок и fenced code block. +- Node.js и часть PHP/Python задач всё ещё иногда падали на уровне AI endpoint с `HTTP 400 invalid JSON: unexpected EOF`. +- Ruby с задачей про greeting показал английский заголовок `LLM what it does`, то есть prompt ещё не полностью зафиксировал язык заголовка. + +### Вывод +- Структура ответа уже заметно улучшилась на части кейсов. +- Нужно отдельно добить два оставшихся класса проблем: + - endpoint иногда не отдает ответ на Node/часть других задач + - LLM иногда переводит заголовок explanation на английский + +### Что изменено в промпте после итерации 5 +- Усилен запрет на любые альтернативные заголовки, включая английский перевод. +- Подчёркнуто, что допустим только literal `LLM что делает`. + +## Итерация 6 + +### Промпт +- Очень короткие задачи с жёстким форматом ответа. +- Node.js, Python, PHP, Ruby. + +### Прогон +- Node.js, Python, PHP, Ruby. + +### Результат +- Python, PHP и Ruby уже стабильно выдали fenced code block, точный `LLM что делает` и синтаксически корректный код. +- Node.js тоже перестал падать на уровне AI endpoint и начал возвращать код. +- Node.js всё ещё печатает заголовок как `LLM Что делает` с заглавной буквой `Что`, то есть строгая фиксация регистра ещё не дожата. +- Где код был получен, lint проходил успешно. + +### Вывод +- Prompt почти готов: структура и код в основном держатся, кроме единичного расхождения в регистре заголовка на Node.js. +- Следующий шаг — явно зафиксировать lower-case `что` и ещё раз прогнать короткий цикл. + +### Что изменено в промпте после итерации 6 +- Добавлено жёсткое указание на точный регистр заголовка `LLM что делает`. +- Запрещены любые заголовки с иным регистром или переводом. + +## Итерация 7 + +### Промпт +- Короткие задачи с минимальной формулировкой. +- Жёстко фиксированный `LLM что делает`. + +### Прогон +- Node.js, Python, PHP, Ruby. + +### Результат +- Все четыре языка вернули code block и точный заголовок `LLM что делает`. +- Линтер везде показал `✅ Синтаксис корректен.` +- Node.js перестал падать на уровне AI endpoint даже при коротком prompt. +- Формат ответа стал предсказуемым: normalization + fenced code block + explanation. + +### Вывод +- Текущий prompt уже работает на коротких и простых кейсах. +- Следующий шаг — проверить его на более разнообразных задачах, чтобы убедиться, что шаблон не ломается за пределами приветствий. diff --git a/LLM/LLM_PROMPT_UNIVERSAL_DRAFT_2026-04-28.md b/LLM/LLM_PROMPT_UNIVERSAL_DRAFT_2026-04-28.md new file mode 100644 index 0000000..e6320b8 --- /dev/null +++ b/LLM/LLM_PROMPT_UNIVERSAL_DRAFT_2026-04-28.md @@ -0,0 +1,72 @@ +# Универсальный промпт для генерации кода функций + +Дата: 2026-04-28 +Статус: черновик для цикла обучения + +## Промпт + +Ты генерируешь код одной функции для Fission. + +Требования: +- Сначала коротко нормализуй задачу, если ввод пользователя кривой, неполный или противоречивый. +- Если не хватает критичного условия, задай один короткий уточняющий вопрос. Если можно безопасно вывести разумное поведение, сделай это и явно опиши допущение. +- Не выдумывай лишнюю инфраструктуру. +- Генерируй только код функции и минимально необходимый контекст для запуска. +- Код должен быть синтаксически корректным для выбранного языка. +- Код должен соответствовать контракту runtime этого языка. +- Не добавляй wrapper-код, который ломает открытие/редактирование исходника. +- Не добавляй лишний вывод, комментарии или объяснения внутри кода. +- Ответ должен иметь строго 3 секции в таком порядке: + 1. Нормализованная задача + 2. Код в fenced block + 3. `LLM что делает` +- В секции кода используй ровно один fenced block с языковым идентификатором. +- В секции `LLM что делает` используй ровно literal string `LLM что делает` с тем же регистром и пробелами; не используй `**`, `Explanation`, английский перевод этого заголовка, другой регистр, другие заголовки или fenced block. +- Сразу после заголовка дай 2-5 коротких пунктов обычным текстом. +- Если код не соответствует задаче, не маскируй это: скажи, что именно изменил и почему. + +Формат ответа: +1. Нормализованная задача. +2. Один fenced code block. +3. Ровно заголовок `LLM что делает`. +4. 2-5 коротких пунктов с объяснением. +5. Если есть допущения, перечисли их отдельно. + +## Языковые правила + +### Node.js +- Используй обычный CommonJS-код функции. +- Экспорт должен быть совместим с runtime Fission. +- Не вставляй ESM export-обвязку в пользовательский код. +- Функция должна возвращать объект ответа или совместимый результат. +- Не придумывай нестандартные поля или внешние SDK, если они не были явно указаны. + +### Python +- Используй обычную функцию entrypoint. +- Не добавляй лишние импорты. +- Не усложняй код без необходимости. +- Не придумывай чужие фреймворки или классы, если задача просит простой handler. + +### Go +- Пиши только синтаксически корректный код. +- Следи за package, imports и сигнатурами. +- Не добавляй мусорный boilerplate. +- Не ломай контракт функции выдуманными обертками или неподходящими типами. + +### PHP +- Возвращай корректный PHP-код функции. +- Следи за открывающим тегом и синтаксисом массива/строк. +- Не выдумывай несуществующие `Fission\\Contract` классы или SDK, если задача про обычную функцию. + +### Ruby +- Возвращай корректный Ruby-код функции. +- Следи за `def`/`end` и структурой хеша. +- Не добавляй лишние внешние зависимости, если они не нужны задаче. + +## Правило проверки + +Любой сгенерированный ответ после этого промпта проходит два шага: +- syntax/lint check +- проверка "LLM что делает" + +Если хотя бы один шаг не проходит, промпт считается недоработанным и должен быть исправлен. diff --git a/LLM/README.md b/LLM/README.md new file mode 100644 index 0000000..348d683 --- /dev/null +++ b/LLM/README.md @@ -0,0 +1,24 @@ +# LLM Workspace + +Эта папка содержит все материалы, связанные с циклом обучения и проверки промптов для генерации кода. + +## Содержимое + +- `LLM_PROMPT_TRAINING_2026-04-28.md` — журнал итераций, выводы и изменения промпта. +- `LLM_PROMPT_UNIVERSAL_DRAFT_2026-04-28.md` — текущий универсальный черновик промпта. +- `llm_prompt_round1.py` .. `llm_prompt_round7.py` — harness-скрипты для прогонов LLM по разным наборам задач. +- `test_ai_ask.py` — базовый набор запросов к LLM ask endpoint. +- `test_llm_syntax.sh` — syntax/lint suite для проверки сгенерированного кода. + +## Правило работы + +- Сначала фиксировать промпт и замысел в документации. +- Потом прогонять много разнотипных задач. +- Обязательно проверять сгенерированный код линтером. +- Отдельно проверять смысл ответа через блок `LLM что делает`. +- После каждого цикла обновлять промпт и журнал. + +## Что не хранится здесь + +- Секреты и токены. +- Не относящиеся к LLM материалы. diff --git a/LLM/llm_prompt_round1.py b/LLM/llm_prompt_round1.py new file mode 100644 index 0000000..23834de --- /dev/null +++ b/LLM/llm_prompt_round1.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import json +import re +import urllib.request + +ASK_API = "https://fission.kube5s.ru/console/api/ai/ask" +CHECK_API = "https://fission.kube5s.ru/console/api/ai/check" +HEADERS = {"X-Test-Sub": "livetest@test.local", "Content-Type": "application/json"} + +CASES = [ + { + "lang": "nodejs", + "question": "Сгенерируй только код Fission-функции на Node.js. Задача: принять JSON с полем name и вернуть ответ 200 с приветствием. Код должен быть синтаксически корректным. После кода отдельным блоком кратко объясни, что делает код.", + }, + { + "lang": "python", + "question": "Сгенерируй только код Fission-функции на Python. Задача: принять словарь context, взять context['name'] если он есть и вернуть строку приветствия. Код должен быть синтаксически корректным. После кода отдельным блоком кратко объясни, что делает код.", + }, + { + "lang": "php", + "question": "Сгенерируй только код Fission-функции на PHP. Задача: вернуть JSON-ответ 200 с приветствием, если имя есть в context. Код должен быть синтаксически корректным. После кода отдельным блоком кратко объясни, что делает код.", + }, + { + "lang": "ruby", + "question": "Сгенерируй только код Fission-функции на Ruby. Задача: принять context и вернуть greeting JSON с именем из context, если оно есть. Код должен быть синтаксически корректным. После кода отдельным блоком кратко объясни, что делает код.", + }, + { + "lang": "go", + "question": "Сгенерируй только код Fission-функции на Go. Задача: принять запрос и вернуть 200 с приветствием, если имя передано, иначе использовать значение по умолчанию. Код должен быть синтаксически корректным. После кода отдельным блоком кратко объясни, что делает код.", + }, +] + + +def post(url: str, payload: dict) -> dict: + body = json.dumps(payload).encode() + req = urllib.request.Request(url, data=body, headers=HEADERS) + with urllib.request.urlopen(req, timeout=90) as resp: + return json.loads(resp.read()) + + +def extract_code(answer: str) -> str: + match = re.search(r"```(?:[a-zA-Z0-9_+-]+)?\n(.*?)\n```", answer, re.S) + return match.group(1).strip() if match else "" + + +def explain_block(answer: str) -> str: + parts = re.split(r"```\s*$", answer, maxsplit=1) + if len(parts) > 1: + return parts[1].strip() + return "" + + +for case in CASES: + ask_resp = post(ASK_API, {"question": case["question"]}) + answer = ask_resp.get("answer", ask_resp.get("error", "???")) + code = extract_code(answer) + explanation = explain_block(answer) + check_resp = post(CHECK_API, {"language": case["lang"], "code": code}) if code else {"ok": False, "result": "no code extracted"} + + print(f"=== {case['lang']} ===") + print("PROMPT:") + print(case["question"]) + print("\nANSWER:") + print(answer) + print("\nEXTRACTED CODE:") + print(code) + print("\nEXPLANATION FOUND:", bool(explanation)) + if explanation: + print(explanation) + print("\nLINT:") + print(json.dumps(check_resp, ensure_ascii=False, indent=2)[:2000]) + print("\n" + "=" * 72 + "\n") diff --git a/LLM/llm_prompt_round2.py b/LLM/llm_prompt_round2.py new file mode 100644 index 0000000..6cf2b59 --- /dev/null +++ b/LLM/llm_prompt_round2.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +import json +import re +import urllib.request +import urllib.error + +ASK_API = "https://fission.kube5s.ru/console/api/ai/ask" +CHECK_API = "https://fission.kube5s.ru/console/api/ai/check" +HEADERS = {"X-Test-Sub": "livetest@test.local", "Content-Type": "application/json"} + +PROMPT = """Ты генерируешь код одной функции для Fission. + +Требования: +- Если ввод кривой, коротко нормализуй задачу. +- Если не хватает важного условия, задай один короткий вопрос или явно опиши допущение. +- Верни только код функции и отдельный блок LLM что делает. +- Код должен быть синтаксически корректным и соответствовать runtime выбранного языка. +- Не добавляй лишний wrapper-код, лишние зависимости или выдуманные SDK. + +Языковые правила: +- Node.js: CommonJS без ESM export-обвязки. +- Python: обычный entrypoint без лишних импортов. +- PHP: обычный PHP-код без выдуманных контрактов. +- Ruby: корректный def/end без лишних зависимостей. +""" + +CASES = [ + { + "lang": "nodejs", + "question": "Сделай Fission Node.js функцию: вернуть 200 с приветствием по полю name или по умолчанию. Потом дай код и блок LLM что делает.", + }, + { + "lang": "python", + "question": "Юзер сказал только 'сделай функцию, которая возвращает приветствие'. Сделай безопасный вариант для Fission на Python: взять context['name'], если он есть, иначе вернуть приветствие по умолчанию. Сначала нормализуй задачу, потом код, потом блок LLM что делает.", + }, + { + "lang": "php", + "question": "Юзер просит Fission-функцию на PHP, но не знает, какой контракт у runtime. Сделай обычный PHP-обработчик: если в context есть name, вернуть JSON 200 с greeting, иначе использовать значение по умолчанию. Не выдумывай фреймворки. Потом код и блок LLM что делает.", + }, + { + "lang": "ruby", + "question": "Юзер написал криво: 'нужна функция чтобы приветить человека и не упасть если данных нет'. Сделай Fission-функцию на Ruby: принять context, безопасно достать name, вернуть greeting JSON. Потом код и блок LLM что делает.", + }, +] + + +def post(url: str, payload: dict) -> dict: + body = json.dumps(payload).encode() + req = urllib.request.Request(url, data=body, headers=HEADERS) + try: + with urllib.request.urlopen(req, timeout=90) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as exc: + return {"http_error": exc.code, "body": exc.read().decode(errors="replace")} + + +def extract_code(answer: str) -> str: + match = re.search(r"```(?:[a-zA-Z0-9_+-]+)?\n(.*?)\n```", answer, re.S) + return match.group(1).strip() if match else "" + + +def explanation_tail(answer: str) -> str: + pieces = answer.split("```", 2) + if len(pieces) >= 3: + return pieces[2].strip() + return "" + + +for case in CASES: + ask_resp = post(ASK_API, {"question": PROMPT + "\n\nЗадача:\n" + case["question"]}) + if ask_resp.get("http_error"): + print(f"=== {case['lang']} HTTP {ask_resp['http_error']} ===") + print(ask_resp.get("body", "")) + print("\n" + "=" * 72 + "\n") + continue + answer = ask_resp.get("answer", ask_resp.get("error", "???")) + code = extract_code(answer) + explanation = explanation_tail(answer) + check_resp = post(CHECK_API, {"language": case["lang"], "code": code}) if code else {"ok": False, "result": "no code extracted"} + + print(f"=== {case['lang']} ===") + print("QUESTION:") + print(case["question"]) + print("\nANSWER:") + print(answer) + print("\nEXTRACTED CODE:") + print(code) + print("\nHAS LLM heading:", "LLM что делает" in answer) + print("HAS explanation tail:", bool(explanation)) + if explanation: + print(explanation[:800]) + print("\nLINT:") + print(json.dumps(check_resp, ensure_ascii=False, indent=2)[:2000]) + print("\n" + "=" * 72 + "\n") diff --git a/LLM/llm_prompt_round3.py b/LLM/llm_prompt_round3.py new file mode 100644 index 0000000..92564fd --- /dev/null +++ b/LLM/llm_prompt_round3.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +import json +import re +import urllib.error +import urllib.request + +ASK_API = "https://fission.kube5s.ru/console/api/ai/ask" +CHECK_API = "https://fission.kube5s.ru/console/api/ai/check" +HEADERS = {"X-Test-Sub": "livetest@test.local", "Content-Type": "application/json"} + +PROMPT = """Ты генерируешь код одной функции для Fission. + +Правила: +- Если ввод кривой, сначала коротко нормализуй задачу. +- Если не хватает важного условия, задай один короткий вопрос или опиши допущение. +- Верни только нормализованную задачу, код и отдельный блок LLM что делает. +- Код должен быть синтаксически корректным и соответствовать runtime выбранного языка. +- Не добавляй лишний wrapper-код, лишние зависимости или выдуманные SDK. +- Заголовок блока объяснения должен быть ровно LLM что делает. + +Языковые правила: +- Node.js: CommonJS без ESM export-обвязки. +- Python: обычный entrypoint без лишних импортов. +- PHP: обычный PHP-код без выдуманных контрактов. +- Ruby: корректный def/end без лишних зависимостей. +""" + +CASES = [ + { + "lang": "nodejs", + "question": "Юзер просит 'сделай функцию которая приветит и не упадёт если имя не пришло'. Нужна Fission Node.js функция: принять JSON, взять name если оно есть, иначе использовать default, вернуть 200 и JSON-ответ. Если условия неполные — нормализуй их сам. Потом код и блок LLM что делает.", + }, + { + "lang": "nodejs", + "question": "Нужно обработать кривой input: пользователь сказал только 'верни сумму чисел'. Сделай Fission Node.js функцию, которая принимает массив numbers, считает сумму и возвращает JSON с result. Если numbers нет или это не массив — используй пустой массив. Потом код и блок LLM что делает.", + }, + { + "lang": "python", + "question": "Юзер хочет Fission-функцию на Python, но сказал только 'преобразуй данные'. Сделай безопасный вариант: если в context есть name, верни greeting; если нет — верни default greeting. Потом код и блок LLM что делает.", + }, + { + "lang": "python", + "question": "Нужна функция, которая суммирует числа из payload, но юзер не указал формат. Сделай Fission Python handler: если в context есть numbers, сложи их; если нет — верни 0. Потом код и блок LLM что делает.", + }, + { + "lang": "php", + "question": "Юзер криво формулирует задачу: 'нужен JSON и приветствие'. Сделай обычный PHP-обработчик Fission: если в context есть name, вернуть JSON 200 с greeting, иначе default greeting. Потом код и блок LLM что делает.", + }, + { + "lang": "php", + "question": "Нужна функция на PHP для подсчёта суммы, но юзер не указал входные данные. Сделай безопасный обработчик: если в context есть numbers и это массив, посчитай сумму, иначе верни 0. Потом код и блок LLM что делает.", + }, + { + "lang": "ruby", + "question": "Юзер просит 'сделай что-то с именем, но если его нет — не падай'. Сделай Fission Ruby функцию: безопасно достать name из context, вернуть JSON с greeting, при отсутствии имени использовать default. Потом код и блок LLM что делает.", + }, + { + "lang": "ruby", + "question": "Нужна функция на Ruby для суммирования чисел, но вход кривой. Сделай обработчик Fission: взять numbers из context, если это массив — просуммировать, иначе вернуть 0. Потом код и блок LLM что делает.", + }, +] + + +def post(url: str, payload: dict) -> dict: + body = json.dumps(payload).encode() + req = urllib.request.Request(url, data=body, headers=HEADERS) + try: + with urllib.request.urlopen(req, timeout=90) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as exc: + return {"http_error": exc.code, "body": exc.read().decode(errors="replace")} + + +def extract_code(answer: str) -> str: + match = re.search(r"```(?:[a-zA-Z0-9_+-]+)?\n(.*?)\n```", answer, re.S) + return match.group(1).strip() if match else "" + + +def explanation_block(answer: str) -> str: + match = re.search(r"(^|\n)LLM что делает\s*\n(.*)$", answer, re.S) + return match.group(2).strip() if match else "" + + +for case in CASES: + ask_resp = post(ASK_API, {"question": PROMPT + "\n\nЗадача:\n" + case["question"]}) + if ask_resp.get("http_error"): + print(f"=== {case['lang']} HTTP {ask_resp['http_error']} ===") + print(ask_resp.get("body", "")) + print("\n" + "=" * 72 + "\n") + continue + + answer = ask_resp.get("answer", ask_resp.get("error", "???")) + code = extract_code(answer) + explanation = explanation_block(answer) + lint_resp = post(CHECK_API, {"language": case["lang"], "code": code}) if code else {"ok": False, "result": "no code extracted"} + + print(f"=== {case['lang']} ===") + print("QUESTION:") + print(case["question"]) + print("\nHAS EXPLICIT HEADING:", "LLM что делает" in answer) + print("HAS PARSED EXPLANATION:", bool(explanation)) + print("\nANSWER:") + print(answer) + print("\nEXTRACTED CODE:") + print(code) + if explanation: + print("\nEXPLANATION:") + print(explanation[:800]) + print("\nLINT:") + print(json.dumps(lint_resp, ensure_ascii=False, indent=2)[:2000]) + print("\n" + "=" * 72 + "\n") \ No newline at end of file diff --git a/LLM/llm_prompt_round4.py b/LLM/llm_prompt_round4.py new file mode 100644 index 0000000..4bb3acb --- /dev/null +++ b/LLM/llm_prompt_round4.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +import json +import re +import urllib.error +import urllib.request + +ASK_API = "https://fission.kube5s.ru/console/api/ai/ask" +CHECK_API = "https://fission.kube5s.ru/console/api/ai/check" +HEADERS = {"X-Test-Sub": "livetest@test.local", "Content-Type": "application/json"} + +PROMPT = """Ты генерируешь код одной функции для Fission. + +Правила ответа: +- Если ввод кривой, коротко нормализуй задачу. +- Если не хватает важного условия, задай один короткий вопрос или опиши допущение. +- Верни только нормализованную задачу, код и отдельный блок LLM что делает. +- Код должен быть синтаксически корректным и соответствовать runtime выбранного языка. +- Не добавляй лишний wrapper-код, лишние зависимости или выдуманные SDK. +- Раздел LLM что делает должен быть обычным текстом: без **, без markdown-кодблоков, без альтернативных заголовков. + +Языковые правила: +- Node.js: CommonJS без ESM export-обвязки. +- Python: обычный entrypoint без лишних импортов. +- PHP: обычный PHP-код без выдуманных контрактов. +- Ruby: корректный def/end без лишних зависимостей. +""" + +CASES = [ + ("nodejs", "Юзер сказал криво: приветствие, default name, не падать на пустом входе. Сделай Fission Node.js handler, который берёт name из JSON и отвечает 200 с greeting."), + ("python", "Юзер просит обработать кривой input. Сделай Fission Python handler: если в context есть name, вернуть greeting, иначе default greeting."), + ("php", "Юзер хочет JSON и приветствие. Сделай PHP-обработчик Fission: name из context, иначе default, ответ 200 и JSON body."), + ("ruby", "Юзер хочет функцию, которая приветствует человека и не падает если name нет. Сделай Ruby handler для Fission с JSON greeting и default name."), +] + + +def post(url: str, payload: dict) -> dict: + body = json.dumps(payload).encode() + req = urllib.request.Request(url, data=body, headers=HEADERS) + try: + with urllib.request.urlopen(req, timeout=90) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as exc: + return {"http_error": exc.code, "body": exc.read().decode(errors="replace")} + + +def extract_code(answer: str) -> str: + match = re.search(r"```(?:[a-zA-Z0-9_+-]+)?\n(.*?)\n```", answer, re.S) + return match.group(1).strip() if match else "" + + +for lang, question in CASES: + ask_resp = post(ASK_API, {"question": PROMPT + "\n\nЗадача:\n" + question}) + print(f"=== {lang} ===") + if ask_resp.get("http_error"): + print(f"HTTP {ask_resp['http_error']}") + print(ask_resp.get("body", "")) + print("\n" + "=" * 72 + "\n") + continue + + answer = ask_resp.get("answer", ask_resp.get("error", "???")) + code = extract_code(answer) + lint_resp = post(CHECK_API, {"language": lang, "code": code}) if code else {"ok": False, "result": "no code extracted"} + + print("QUESTION:") + print(question) + print("\nHAS EXACT HEADING:", bool(re.search(r"(^|\n)LLM что делает\s*$", answer, re.M))) + print("ANSWER:") + print(answer) + print("\nEXTRACTED CODE:") + print(code) + print("\nLINT:") + print(json.dumps(lint_resp, ensure_ascii=False, indent=2)[:2000]) + print("\n" + "=" * 72 + "\n") diff --git a/LLM/llm_prompt_round5.py b/LLM/llm_prompt_round5.py new file mode 100644 index 0000000..322f650 --- /dev/null +++ b/LLM/llm_prompt_round5.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +import json +import re +import urllib.error +import urllib.request + +ASK_API = "https://fission.kube5s.ru/console/api/ai/ask" +CHECK_API = "https://fission.kube5s.ru/console/api/ai/check" +HEADERS = {"X-Test-Sub": "livetest@test.local", "Content-Type": "application/json"} + +PROMPT = """Ты генерируешь код одной функции для Fission. + +Нужно отвечать строго в 3 секциях: +1. Нормализованная задача +2. Один fenced code block +3. Заголовок LLM что делает, затем 2-5 коротких пунктов + +Правила: +- Если ввод кривой, сначала коротко нормализуй задачу. +- Если не хватает важного условия, задай один короткий вопрос или опиши допущение. +- Не выдумывай лишнюю инфраструктуру, SDK или wrapper-код. +- Код должен быть синтаксически корректным и соответствовать runtime выбранного языка. +- В секции кода используй ровно один fenced block с языковым идентификатором. +- В секции LLM что делает не используй другие заголовки, bold, Explanation или fenced block. + +Языковые правила: +- Node.js: CommonJS без ESM export-обвязки. +- Python: обычный entrypoint без лишних импортов. +- PHP: обычный PHP-код без выдуманных контрактов. +- Ruby: корректный def/end без лишних зависимостей. +""" + +CASES = [ + ("nodejs", "Юзер криво сказал: приветствие и не падать если name пустой. Сделай Fission Node.js handler, который читает JSON body, берёт name и отвечает 200 с greeting и default World."), + ("nodejs", "Нужна функция для суммы чисел, но формат входа не описан. Сделай Node.js Fission handler: если в JSON есть numbers и это массив, вернуть сумму, иначе 0."), + ("python", "Нужна Fission Python функция: если в context есть name, вернуть greeting, иначе default greeting. Ничего лишнего."), + ("python", "Юзер просит обработать число и не знает формат. Сделай Python handler для Fission: если в context есть numbers, просуммировать их, иначе вернуть 0."), + ("php", "Сделай PHP handler для Fission: name из context, иначе World, ответ JSON 200 с greeting. Без фреймворков."), + ("php", "Сделай PHP handler для Fission: если в context есть numbers-массив, вернуть его сумму, иначе 0. Без лишних зависимостей."), + ("ruby", "Сделай Ruby handler для Fission: name из context, иначе World, ответ JSON с greeting."), + ("ruby", "Сделай Ruby handler для Fission: если в context есть numbers и это массив, вернуть сумму, иначе 0."), +] + + +def post(url: str, payload: dict) -> dict: + body = json.dumps(payload).encode() + req = urllib.request.Request(url, data=body, headers=HEADERS) + try: + with urllib.request.urlopen(req, timeout=90) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as exc: + return {"http_error": exc.code, "body": exc.read().decode(errors="replace")} + + +def extract_code(answer: str) -> str: + match = re.search(r"```(?:[a-zA-Z0-9_+-]+)?\n(.*?)\n```", answer, re.S) + return match.group(1).strip() if match else "" + + +def has_exact_heading(answer: str) -> bool: + return bool(re.search(r"(^|\n)LLM что делает\s*$", answer, re.M)) + + +def explanation_after_heading(answer: str) -> str: + match = re.search(r"(^|\n)LLM что делает\s*\n(.*)$", answer, re.S) + return match.group(2).strip() if match else "" + + +for lang, question in CASES: + ask_resp = post(ASK_API, {"question": PROMPT + "\n\nЗадача:\n" + question}) + print(f"=== {lang} ===") + print("QUESTION:") + print(question) + + if ask_resp.get("http_error"): + print(f"HTTP {ask_resp['http_error']}") + print(ask_resp.get("body", "")) + print("\n" + "=" * 72 + "\n") + continue + + answer = ask_resp.get("answer", ask_resp.get("error", "???")) + code = extract_code(answer) + lint_resp = post(CHECK_API, {"language": lang, "code": code}) if code else {"ok": False, "result": "no code extracted"} + + print("HAS EXACT HEADING:", has_exact_heading(answer)) + print("HAS EXPLANATION:", bool(explanation_after_heading(answer))) + print("ANSWER:") + print(answer) + print("\nEXTRACTED CODE:") + print(code) + print("\nLINT:") + print(json.dumps(lint_resp, ensure_ascii=False, indent=2)[:2000]) + print("\n" + "=" * 72 + "\n") diff --git a/LLM/llm_prompt_round6.py b/LLM/llm_prompt_round6.py new file mode 100644 index 0000000..0efa757 --- /dev/null +++ b/LLM/llm_prompt_round6.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import json +import re +import urllib.error +import urllib.request + +ASK_API = "https://fission.kube5s.ru/console/api/ai/ask" +CHECK_API = "https://fission.kube5s.ru/console/api/ai/check" +HEADERS = {"X-Test-Sub": "livetest@test.local", "Content-Type": "application/json"} + +PROMPT = """Ты генерируешь код одной функции для Fission. + +Требования к ответу: +- Сначала коротко нормализуй задачу. +- Потом выдай один fenced code block. +- Потом выдай ровно заголовок LLM что делает и 2-5 коротких пунктов. +- Не используй bold, Explanation, английский перевод заголовка, дополнительные заголовки или fenced block вокруг объяснения. +- Не выдумывай лишние SDK, фреймворки или wrapper-код. +- Код должен быть синтаксически корректным для выбранного языка и соответствовать runtime. + +Языковые правила: +- Node.js: CommonJS без ESM export-обвязки. +- Python: обычный entrypoint. +- PHP: обычный PHP-код. +- Ruby: корректный def/end. +""" + +CASES = [ + ("nodejs", "Сделай Fission Node.js handler: name из JSON body, иначе World, ответ 200 с greeting."), + ("python", "Сделай Fission Python handler: name из context, иначе World, ответ greeting."), + ("php", "Сделай Fission PHP handler: name из context, иначе World, ответ 200 JSON greeting."), + ("ruby", "Сделай Fission Ruby handler: name из context, иначе World, ответ greeting JSON."), +] + + +def post(url: str, payload: dict) -> dict: + body = json.dumps(payload).encode() + req = urllib.request.Request(url, data=body, headers=HEADERS) + try: + with urllib.request.urlopen(req, timeout=90) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as exc: + return {"http_error": exc.code, "body": exc.read().decode(errors="replace")} + + +def extract_code(answer: str) -> str: + match = re.search(r"```(?:[a-zA-Z0-9_+-]+)?\n(.*?)\n```", answer, re.S) + return match.group(1).strip() if match else "" + + +for lang, question in CASES: + ask_resp = post(ASK_API, {"question": PROMPT + "\n\nЗадача:\n" + question}) + print(f"=== {lang} ===") + print("QUESTION:") + print(question) + + if ask_resp.get("http_error"): + print(f"HTTP {ask_resp['http_error']}") + print(ask_resp.get("body", "")) + print("\n" + "=" * 72 + "\n") + continue + + answer = ask_resp.get("answer", ask_resp.get("error", "???")) + code = extract_code(answer) + lint_resp = post(CHECK_API, {"language": lang, "code": code}) if code else {"ok": False, "result": "no code extracted"} + + print("HAS EXACT HEADING:", bool(re.search(r"(^|\n)LLM что делает\s*$", answer, re.M))) + print("ANSWER:") + print(answer) + print("\nEXTRACTED CODE:") + print(code) + print("\nLINT:") + print(json.dumps(lint_resp, ensure_ascii=False, indent=2)[:2000]) + print("\n" + "=" * 72 + "\n") diff --git a/LLM/llm_prompt_round7.py b/LLM/llm_prompt_round7.py new file mode 100644 index 0000000..0fd6763 --- /dev/null +++ b/LLM/llm_prompt_round7.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +import json +import re +import urllib.error +import urllib.request + +ASK_API = "https://fission.kube5s.ru/console/api/ai/ask" +CHECK_API = "https://fission.kube5s.ru/console/api/ai/check" +HEADERS = {"X-Test-Sub": "livetest@test.local", "Content-Type": "application/json"} + +PROMPT = """Ты генерируешь код одной функции для Fission. + +Требования: +- Сначала коротко нормализуй задачу. +- Потом выдай один fenced code block. +- Потом выдай ровно заголовок LLM что делает и 2-5 коротких пунктов. +- Не используй bold, Explanation, английский перевод заголовка, дополнительные заголовки или fenced block вокруг объяснения. +- Не выдумывай лишние SDK, фреймворки или wrapper-код. +- Код должен быть синтаксически корректным для выбранного языка и соответствовать runtime. + +Языковые правила: +- Node.js: CommonJS без ESM export-обвязки. +- Python: обычный entrypoint. +- PHP: обычный PHP-код. +- Ruby: корректный def/end. +""" + +CASES = [ + ("nodejs", "Сделай Fission Node.js handler: name из JSON body, иначе World, ответ 200 с greeting."), + ("nodejs", "Сделай Fission Node.js handler: если в JSON есть numbers и это массив, вернуть сумму, иначе 0."), + ("python", "Сделай Fission Python handler: name из context, иначе World, ответ greeting."), + ("python", "Сделай Fission Python handler: если в context есть numbers, вернуть сумму, иначе 0."), + ("php", "Сделай Fission PHP handler: name из context, иначе World, ответ 200 JSON greeting."), + ("php", "Сделай Fission PHP handler: если в context есть numbers-массив, вернуть его сумму, иначе 0."), + ("ruby", "Сделай Fission Ruby handler: name из context, иначе World, ответ greeting JSON."), + ("ruby", "Сделай Fission Ruby handler: если в context есть numbers и это массив, вернуть сумму, иначе 0."), +] + + +def post(url: str, payload: dict) -> dict: + body = json.dumps(payload).encode() + req = urllib.request.Request(url, data=body, headers=HEADERS) + try: + with urllib.request.urlopen(req, timeout=90) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as exc: + return {"http_error": exc.code, "body": exc.read().decode(errors="replace")} + + +def extract_code(answer: str) -> str: + match = re.search(r"```(?:[a-zA-Z0-9_+-]+)?\n(.*?)\n```", answer, re.S) + return match.group(1).strip() if match else "" + + +for lang, question in CASES: + ask_resp = post(ASK_API, {"question": PROMPT + "\n\nЗадача:\n" + question}) + print(f"=== {lang} ===") + print("QUESTION:") + print(question) + + if ask_resp.get("http_error"): + print(f"HTTP {ask_resp['http_error']}") + print(ask_resp.get("body", "")) + print("\n" + "=" * 72 + "\n") + continue + + answer = ask_resp.get("answer", ask_resp.get("error", "???")) + code = extract_code(answer) + lint_resp = post(CHECK_API, {"language": lang, "code": code}) if code else {"ok": False, "result": "no code extracted"} + + print("HAS EXACT HEADING:", bool(re.search(r"(^|\n)LLM что делает\s*$", answer, re.M))) + print("ANSWER:") + print(answer) + print("\nEXTRACTED CODE:") + print(code) + print("\nLINT:") + print(json.dumps(lint_resp, ensure_ascii=False, indent=2)[:2000]) + print("\n" + "=" * 72 + "\n") diff --git a/test_ai_ask.py b/LLM/test_ai_ask.py similarity index 100% rename from test_ai_ask.py rename to LLM/test_ai_ask.py diff --git a/test_llm_syntax.sh b/LLM/test_llm_syntax.sh similarity index 78% rename from test_llm_syntax.sh rename to LLM/test_llm_syntax.sh index a53a2ff..a5ce1f6 100755 --- a/test_llm_syntax.sh +++ b/LLM/test_llm_syntax.sh @@ -52,6 +52,36 @@ check() { fi } +check_error_contains() { + local id="$1" + local desc="$2" + local lang="$3" + local code="$4" + local must_contain="$5" + + local escaped + escaped=$(printf '%s' "$code" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))") + + local body="{\"language\":\"${lang}\",\"code\":${escaped}}" + local resp + resp=$(curl -s -X POST "$API" \ + -H "X-Test-Sub: livetest@test.local" \ + -H "Content-Type: application/json" \ + -d "$body" 2>/dev/null) + + local ok_val result_val + ok_val=$(echo "$resp" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('ok','?'))" 2>/dev/null) + result_val=$(echo "$resp" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',''))" 2>/dev/null) + + if [[ "$ok_val" == "False" ]] && echo "$result_val" | grep -qi "$must_contain"; then + echo "[PASS] $id | $lang | expect error containing=$must_contain | $desc" + else + echo "[FAIL] $id | $lang | expected error containing=$must_contain got=$ok_val | $desc" + echo " resp: $(echo "$result_val" | head -c 160)" + ((FAIL++)) + fi +} + hallucination_check() { local id="$1" local desc="$2" @@ -116,6 +146,18 @@ check N04 "невалидный JS" nodejs fail \ 'const x = @@@; module.exports = function(ctx) { return x; };' +# ERROR TYPE: unexpected token / bad object literal +check_error_contains N07 "невалидный объектный литерал" nodejs \ +'module.exports = function(ctx) { + const data = { a: 1,, b: 2 }; + return data; +};' "Unexpected token" + +# ERROR TYPE: unterminated function / unexpected end +check_error_contains N08 "незакрытая функция" nodejs \ +'module.exports = function(ctx) { + return { status: 200, body: "hello" }' "Unexpected end" + # SYNTAX-ONLY: exports nothing, но синтаксис корректен check N05 "нет module.exports, но синтаксис корректен" nodejs ok \ 'async function doStuff(context) { @@ -153,6 +195,16 @@ check P04 "невалидный синтаксис Python" python fail \ x = @@@ return x' +# ERROR TYPE: expected ':' +check_error_contains P07 "пропущено двоеточие в сигнатуре" python \ +'def main(data) + return "hello"' "expected ':'" + +# ERROR TYPE: '(' was never closed +check_error_contains P08 "не закрыта скобка" python \ +'def main(data: + return "hello"' "was never closed" + # SYNTAX-ONLY: нет main/handler, но синтаксис корректен check P05 "нет main/handler, но синтаксис корректен" python ok \ 'def process(data): @@ -225,6 +277,18 @@ check R02 "синтаксическая ошибка Ruby" ruby fail \ { status: 200 body: "missing comma" } end' +# ERROR TYPE: unexpected token in hash literal +check_error_contains R06 "неверный hash literal" ruby \ +'def handler(context) + { status: 200 body: "missing comma" } +end' "syntax error" + +# ERROR TYPE: unexpected end +check_error_contains R07 "незакрытый вызов/метод" ruby \ +'def handler(context) + value = context[:value] +' "unexpected end-of-input" + # SYNTAX-ONLY: нет handler, но синтаксис корректен check R03 "нет handler, но синтаксис корректен" ruby ok \ 'def process(ctx) @@ -250,6 +314,21 @@ function handler(array $context): array { return ["status" => 200, "body" => "Hello " . $name]; }' +# ERROR TYPE: unexpected EOF +check_error_contains H06 "неожиданный конец файла" php \ +' 200]; +}' "unexpected token" + # SYNTAX-ONLY: нет handler, но синтаксис корректен check H03 "нет функции handler в PHP, но синтаксис корректен" php ok \ ' 200, 'body' => $value];\n}\n" + got, err := decodeArchiveBytesToSource([]byte(src)) + if err != nil { + t.Fatalf("decodeArchiveBytesToSource() error = %v", err) + } + if got != src { + t.Fatalf("decoded source = %q, want %q", got, src) + } +} diff --git a/console/internal/runtime/nodejs.go b/console/internal/runtime/nodejs.go index da791f1..c01b5a9 100644 --- a/console/internal/runtime/nodejs.go +++ b/console/internal/runtime/nodejs.go @@ -9,24 +9,10 @@ import ( // BuildJSDeployZip упаковывает Node.js код в zip для Fission deployment package. // -// Почему zip, а не просто literal-код: -// Fission node-env v3 поддерживает ESM модули. Чтобы Node.js трактовал файл -// как ESM, нужен package.json с {"type":"module"} рядом с main.js. -// Без него `import` синтаксис вызовет "require is not defined" или SyntaxError. -// -// Почему new Function: -// Пользовательский код пишет CJS-стиль (module.exports = ...) но мы хотим ESM wrapper. -// new Function() изолирует module/exports от глобального ESM контекста и позволяет -// исполнять CJS-код без изменений. -// -// Результат: zip с двумя файлами: -// - package.json: {"type":"module"} -// - main.js: ESM wrapper + инлайн пользовательский код через new Function -// -// Совместимость entrypoint: -// UI исторически отправлял entrypoint="handler", а backend по умолчанию использует -// entrypoint="main". Чтобы specialization не ломался из-за несовпадения, -// wrapper экспортирует обе точки входа: named exports main и handler, а также default. +// Генерируем CommonJS wrapper: так Fission/node executor не упирается в ESM export +// синтаксис и может исполнять результат без package.json и type=module. +// Пользовательский код остаётся CJS-стилем через new Function(module, exports). +// Wrapper экспортирует main, handler и default через module.exports. func BuildJSDeployZip(code string) ([]byte, error) { // Сериализуем пользовательский код в JSON строку чтобы безопасно инлайнить // в JavaScript-литерал — экранирует кавычки, переводы строк, спецсимволы. @@ -35,10 +21,10 @@ func BuildJSDeployZip(code string) ([]byte, error) { return nil, fmt.Errorf("marshal user code: %w", err) } - // main.js — ESM wrapper: + // main.js — CommonJS wrapper: // 1. new Function создаёт функцию в пустом модульном контексте (нет import/export) // 2. Передаём ей module и exports как параметры → пользовательский CJS код работает - // 3. Экспортируем default async function для Fission node-env entrypoint + // 3. Экспортируем main/handler/default через module.exports wrapper := fmt.Sprintf(`const __mod = { exports: {} }; (new Function('module', 'exports', %s))(__mod, __mod.exports); const _fn = __mod.exports; @@ -52,22 +38,15 @@ const _fn = __mod.exports; return { status: 200, ...result }; } - export { __invoke as main, __invoke as handler }; - export default __invoke; +module.exports = __invoke; +module.exports.main = __invoke; +module.exports.handler = __invoke; +module.exports.default = __invoke; `, string(codeJSON)) var buf bytes.Buffer zw := zip.NewWriter(&buf) - // package.json: включает ESM режим для всего архива - pkgfw, err := zw.Create("package.json") - if err != nil { - return nil, err - } - if _, err := pkgfw.Write([]byte(`{"type":"module"}`)); err != nil { - return nil, err - } - fw, err := zw.Create("main.js") if err != nil { return nil, err diff --git a/console/ui/index.html b/console/ui/index.html index 86a3bed..d00531b 100644 --- a/console/ui/index.html +++ b/console/ui/index.html @@ -451,6 +451,8 @@ Имя Окружение Пакет + Создана + Изменена Маршрут Методы Действия @@ -808,6 +810,25 @@ .replaceAll("'", '''); } + function formatTimestamp(ts) { + if (!ts) return '—'; + var d = new Date(ts); + if (isNaN(d.getTime())) return String(ts); + return d.toLocaleString('ru-RU', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + } + + function timestampCell(ts) { + var text = formatTimestamp(ts); + return '' + h(text) + ''; + } + const LANG_TEMPLATES = { python: { entrypoint: 'main.main', @@ -936,6 +957,7 @@ }); closeEdit(); progress.stop('\u041a\u043e\u0434 \u043e\u0431\u043d\u043e\u0432\u043b\u0451\u043d: ' + name, 'ok'); + await reloadAll(); } catch (e) { progress.stop('\u041e\u0448\u0438\u0431\u043a\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f: ' + e.message, 'err'); } finally { @@ -1043,6 +1065,8 @@ const rows = (fns || []).map(function (f) { const spec = f.spec || {}; + const meta = f.metadata || {}; + const ann = meta.annotations || {}; const env = (spec.environment && spec.environment.name) || '-'; const pkg = (spec.package && spec.package.packageref && spec.package.packageref.name) || '-'; const name = (f.metadata && f.metadata.name) || '-'; @@ -1050,6 +1074,8 @@ const route = (trig.spec && trig.spec.relativeurl) || '-'; const methods = (trig.spec && trig.spec.methods) || []; const chips = methods.map(function (m) { return '' + h(m) + ''; }).join(''); + const createdAt = ann['fission-console/created-at'] || meta.creationTimestamp || '-'; + const updatedAt = ann['fission-console/updated-at'] || createdAt; var isGo = /go[-_]env/.test(env); var isTf = /^tf-/.test(name); var tfBadge = (isGo || isTf) ? 'TF ' : ''; @@ -1061,18 +1087,20 @@ '' + h(name) + '' + '' + h(env) + '' + '' + h(pkg) + '' + + '' + timestampCell(createdAt) + '' + + '' + timestampCell(updatedAt) + '' + '' + h(route) + '' + '' + chips + '' + '' + actions + '' + ''; }).join(''); - document.getElementById('fn-rows').innerHTML = rows || '\u041d\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0439'; + document.getElementById('fn-rows').innerHTML = rows || '\u041d\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0439'; if (!rows) { - document.getElementById('fn-rows').innerHTML = '\u041d\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0439'; + document.getElementById('fn-rows').innerHTML = '\u041d\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0439'; } } catch (e) { - document.getElementById('fn-rows').innerHTML = 'Load error: ' + e.message + ''; + document.getElementById('fn-rows').innerHTML = 'Load error: ' + e.message + ''; showStatus('\u041e\u0448\u0438\u0431\u043a\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438: ' + e.message, 'err'); } }