251 lines
11 KiB
Go
251 lines
11 KiB
Go
package api
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
const (
|
||
aiModeChat = "chat"
|
||
aiModeCodegen = "codegen"
|
||
aiModeExplain = "explain"
|
||
)
|
||
|
||
type llmMessage struct {
|
||
Role string `json:"role"`
|
||
Content string `json:"content"`
|
||
}
|
||
|
||
type aiPromptRequest struct {
|
||
Mode string `json:"mode"`
|
||
Language string `json:"language"`
|
||
Name string `json:"name"`
|
||
Description string `json:"description"`
|
||
Code string `json:"code"`
|
||
Question string `json:"question"`
|
||
}
|
||
|
||
type llmChatRequest struct {
|
||
Model string `json:"model"`
|
||
Messages []llmMessage `json:"messages"`
|
||
MaxTokens int `json:"max_tokens"`
|
||
}
|
||
|
||
func buildLLMChatRequest(req aiPromptRequest) (llmChatRequest, error) {
|
||
mode := normalizeAIMode(req.Mode)
|
||
lang := normalizeLanguage(req.Language)
|
||
|
||
switch mode {
|
||
case aiModeCodegen:
|
||
if lang == "" {
|
||
return llmChatRequest{}, fmt.Errorf("language is required")
|
||
}
|
||
return llmChatRequest{
|
||
Model: "gpt-oss-120b",
|
||
Messages: []llmMessage{
|
||
{Role: "system", Content: buildCodegenSystemPrompt(lang)},
|
||
{Role: "user", Content: buildCodegenUserPrompt(lang, req.Name, req.Description)},
|
||
},
|
||
MaxTokens: 1024,
|
||
}, nil
|
||
case aiModeExplain:
|
||
if lang == "" {
|
||
return llmChatRequest{}, fmt.Errorf("language is required")
|
||
}
|
||
if strings.TrimSpace(req.Code) == "" {
|
||
return llmChatRequest{}, fmt.Errorf("code is required")
|
||
}
|
||
return llmChatRequest{
|
||
Model: "gpt-oss-120b",
|
||
Messages: []llmMessage{
|
||
{Role: "system", Content: buildExplainSystemPrompt(lang)},
|
||
{Role: "user", Content: buildExplainUserPrompt(lang, req.Code)},
|
||
},
|
||
MaxTokens: 1024,
|
||
}, nil
|
||
default:
|
||
question := strings.TrimSpace(req.Question)
|
||
if question == "" {
|
||
return llmChatRequest{}, fmt.Errorf("question is required")
|
||
}
|
||
return llmChatRequest{
|
||
Model: "gpt-oss-120b",
|
||
Messages: []llmMessage{
|
||
{Role: "system", Content: buildChatSystemPrompt()},
|
||
{Role: "user", Content: question},
|
||
},
|
||
MaxTokens: 1024,
|
||
}, nil
|
||
}
|
||
}
|
||
|
||
func normalizeAIMode(mode string) string {
|
||
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||
case aiModeCodegen:
|
||
return aiModeCodegen
|
||
case aiModeExplain:
|
||
return aiModeExplain
|
||
default:
|
||
return aiModeChat
|
||
}
|
||
}
|
||
|
||
func normalizeLanguage(language string) string {
|
||
return strings.ToLower(strings.TrimSpace(language))
|
||
}
|
||
|
||
func buildCodegenSystemPrompt(lang string) string {
|
||
switch lang {
|
||
case "python":
|
||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||
Генерируй только Fission-usable Python code.
|
||
Канон для Python: файл main.py, entrypoint main.main, функция def main(): без аргументов.
|
||
Не генерируй def main(ctx), def handler, Flask-приложение или обычный CLI-main.
|
||
Если нужен ответ HTTP-подобного вида, возвращай dict со status/body/headers или строку; не выдумывай лишние зависимости.
|
||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||
|
||
case "nodejs":
|
||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||
Генерируй только Fission-usable Node.js code.
|
||
Канон для Node.js: user code должен быть CommonJS-style, обычно module.exports = async function (context) { ... }.
|
||
Не используй ESM import/export в user code. Не пиши обычный CLI main.
|
||
Возвращай объект вида { status, body, headers } или строку/JSON, совместимые с Fission node wrapper.
|
||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||
|
||
case "go":
|
||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||
Генерируй только Fission-usable Go code.
|
||
Канон для Go: package main, экспортированная функция func Handler(w http.ResponseWriter, r *http.Request).
|
||
Не генерируй func main() как entrypoint и не делай CLI-программу.
|
||
Импортируй net/http и другие пакеты только если они реально нужны.
|
||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||
|
||
case "php":
|
||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||
Генерируй только Fission-usable PHP code.
|
||
Канон для PHP: файл main.php, entrypoint main.php::handler, функция function handler($context).
|
||
Не пиши CLI-скрипт и не выдумывай другой entrypoint.
|
||
Для ответа используй $context["response"] или совместимый Fission response pattern.
|
||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||
|
||
case "ruby":
|
||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||
Генерируй только Fission-usable Ruby code.
|
||
Канон для Ruby: файл handler.rb, entrypoint handler, функция def handler.
|
||
Не пиши CLI-скрипт и не выдумывай другой entrypoint.
|
||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||
|
||
default:
|
||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||
Генерируй только Fission-usable code.
|
||
Если язык не распознан, выбери минимальную рабочую реализацию без лишней обвязки.
|
||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||
}
|
||
}
|
||
|
||
func buildCodegenUserPrompt(lang, name, description string) string {
|
||
funcName := strings.TrimSpace(name)
|
||
if funcName == "" {
|
||
funcName = defaultPromptFunctionName(lang)
|
||
}
|
||
goal := strings.TrimSpace(description)
|
||
if goal == "" {
|
||
goal = "Сгенерируй минимальную рабочую Fission-функцию."
|
||
}
|
||
|
||
return strings.TrimSpace(fmt.Sprintf(`Сгенерируй функцию для Fission.
|
||
Language: %s
|
||
Function name: %s
|
||
What to do: %s
|
||
|
||
Правила:
|
||
1) Верни только код.
|
||
2) Соблюдай канон Fission для этого языка.
|
||
3) Если пользователь описал задачу плохо или противоречиво, выбери самый безопасный и минимальный рабочий вариант.
|
||
4) Не добавляй markdown, пояснений и лишних файлов.
|
||
5) Код должен быть пригоден для вставки в console и запуска в Fission без ручной правки entrypoint.`, lang, funcName, goal))
|
||
}
|
||
|
||
func buildExplainSystemPrompt(lang string) string {
|
||
return strings.TrimSpace(fmt.Sprintf(`Ты анализатор кода для Fission Console.
|
||
Отвечай кратко, по делу и без markdown-блоков.
|
||
Сначала объясни, что делает код.
|
||
Затем отдельно скажи, подходит ли он для Fission %s и почему.
|
||
Если entrypoint, сигнатура или return shape не совпадают с каноном Fission, скажи это прямо.
|
||
Не переписывай код и не выдумывай поведение, которого нет.`, prettyLanguageName(lang)))
|
||
}
|
||
|
||
func buildExplainUserPrompt(lang, code string) string {
|
||
return strings.TrimSpace(fmt.Sprintf(`Объясни этот %s код и проверь его на совместимость с Fission.
|
||
|
||
Требования к ответу:
|
||
1) Что делает код.
|
||
2) Совместим ли он с Fission.
|
||
3) Если не совместим — что именно сломано (entrypoint, сигнатура, return, экспорт, файл).
|
||
|
||
Код:
|
||
%s`, prettyLanguageName(lang), strings.TrimSpace(code)))
|
||
}
|
||
|
||
func buildChatSystemPrompt() string {
|
||
return strings.TrimSpace(`Ты ассистент платформы NUBES Fission Console — сервиса для запуска функций (serverless).
|
||
Отвечай кратко, по делу, без технического жаргона.
|
||
|
||
Что ты знаешь:
|
||
- Пользователь пишет функции на Python, Node.js, Go, PHP, Ruby.
|
||
- Функция — это файл с одной функцией-обработчиком (entrypoint). Она принимает запрос и возвращает ответ.
|
||
- Функции можно создавать прямо в браузере (вставить код) или загружать архивом (zip).
|
||
- Функция запускается по HTTP-запросу или по расписанию (cron).
|
||
- Логи функции — это всё, что она выводит в консоль (print, console.log и т.д.).
|
||
- Если функция не отвечает — возможно, она ещё "прогревается" (cold start, первый запуск занимает несколько секунд).
|
||
|
||
Чем помогаешь:
|
||
- Объяснить что делает код функции.
|
||
- Подсказать как написать функцию для конкретной задачи.
|
||
- Помочь разобраться с ошибкой или странным поведением.
|
||
- Объяснить как работает платформа (cron, HTTP-триггеры, логи).
|
||
|
||
Чего не делаешь:
|
||
- Не рассказываешь про устройство платформы изнутри, деплой, инфраструктуру.
|
||
- Не генерируешь код (для этого есть кнопка "Из кода" с AI-генерацией).
|
||
- Не помогаешь с вещами не связанными с функциями и платформой.`)
|
||
}
|
||
|
||
func prettyLanguageName(lang string) string {
|
||
switch normalizeLanguage(lang) {
|
||
case "nodejs":
|
||
return "Node.js"
|
||
case "python":
|
||
return "Python"
|
||
case "go":
|
||
return "Go"
|
||
case "php":
|
||
return "PHP"
|
||
case "ruby":
|
||
return "Ruby"
|
||
default:
|
||
return "этот язык"
|
||
}
|
||
}
|
||
|
||
func defaultPromptFunctionName(lang string) string {
|
||
switch normalizeLanguage(lang) {
|
||
case "go":
|
||
return "Handler"
|
||
case "php":
|
||
return "handler"
|
||
case "ruby":
|
||
return "handler"
|
||
case "nodejs":
|
||
return "main"
|
||
default:
|
||
return "main"
|
||
}
|
||
}
|