Добавил интеграцию с LLM

This commit is contained in:
2026-06-14 12:34:36 +03:00
parent 883148787a
commit 2ec0cf9747
7 changed files with 742 additions and 4 deletions
+72
View File
@@ -0,0 +1,72 @@
package llm
import (
"errors"
"strings"
)
// ErrNoJSON — в тексте не нашлось JSON-объекта.
var ErrNoJSON = errors.New("llm: no JSON object in response")
// ExtractJSONObject вытаскивает первый верхнеуровневый JSON-объект из ответа
// модели. Модели часто оборачивают JSON в ```-ограждения или добавляют
// пояснения до/после — здесь это срезается, а скобки считаются с учётом
// строковых литералов и экранирования, чтобы `{` внутри строки не сбивал
// баланс. Возвращает срез исходного текста (валидацию делает вызывающий).
func ExtractJSONObject(s string) (string, error) {
s = stripCodeFences(s)
start := strings.IndexByte(s, '{')
if start < 0 {
return "", ErrNoJSON
}
depth := 0
inStr := false
esc := false
for i := start; i < len(s); i++ {
c := s[i]
if inStr {
switch {
case esc:
esc = false
case c == '\\':
esc = true
case c == '"':
inStr = false
}
continue
}
switch c {
case '"':
inStr = true
case '{':
depth++
case '}':
depth--
if depth == 0 {
return s[start : i+1], nil
}
}
}
return "", ErrNoJSON
}
// stripCodeFences убирает markdown-ограждение ```...``` (с опциональным
// языковым тегом вроде ```json), если ответ обёрнут в него целиком.
func stripCodeFences(s string) string {
t := strings.TrimSpace(s)
if !strings.HasPrefix(t, "```") {
return s
}
// Отрезаем первую строку с открывающим ``` и языковым тегом.
if nl := strings.IndexByte(t, '\n'); nl >= 0 {
t = t[nl+1:]
} else {
return s
}
if end := strings.LastIndex(t, "```"); end >= 0 {
t = t[:end]
}
return t
}