- BrainTools - https://www.braintools.ru -
В наши дни каждый разработчик, наверняка, пробовал вайбкодить, а некоторые идут дальше и заводят себе целых ИИ агентов. Однако отовсюду доносятся новости о том, как какой-то AI агент удалил базу данных со всеми бэкапами. Поэтому давайте посмотрим исходных код проектов, которые так или иначе связаны с агентной разработкой.

Агентская разработка и различные AI-решения всё теснее входит в разработку программного обеспечения. Поэтому мы решили проверить нашим Go анализатором проекты, которые как-то связаны с нейронными сетями и разработкой с помощью AI. Это различные серверы для запуска локальных моделей, балансировщики нагрузок, прокси-сервисы для подписок и многое другое. К тому же такие проекты достаточно популярные и пользуются спросом.
Если вам интересно собственноручно попробовать наш Go анализатор, то вы можете принять участие в программе раннего доступа [1]. EAP доступно для языков JavaScript, TypeScript и Go.
Как говорится, куй железо, пока горячо, поэтому приступим к проверке проектов.
Начнём с агрегатора и балансировщика нагрузки для множества AI-провайдеров — new-api [2]. Он позволяет управлять десятками API-ключей от разных провайдеров через единый шлюз. Это достаточно крупный и популярный проект (33 тыс. звёзд на GitHub), но даже в таких проектах встречаются баги и подозрительный код. Коммит 22b6b16 [3].
func GetChannel(group string, model string, retry int) (*Channel, error) {
var abilities []Ability
var err error = nil
channelQuery, err := getChannelQuery(group, model, retry)
if err != nil {
return nil, err
}
if common.UsingSQLite || common.UsingPostgreSQL {
err = channelQuery.Order("weight DESC").Find(&abilities).Error
} else {
err = channelQuery.Order("weight DESC").Find(&abilities).Error
}
....
}
Предупреждение PVS-Studio: V8005 [4] The ‘then’ statement is equivalent to the ‘else’ statement. ability.go 114 [5]
Анализатор сообщает, что if с условием common.UsingSQLite || common.UsingPostgreSQL по сути бесполезен, поскольку в then и else части выполняется одинаковый код.
Аналогичная проблема была найдена в следующем отрывке кода:
func StreamResponseClaude2OpenAI(....) .... {
....
if claudeResponse.Type == "message_start" {
....
} else if claudeResponse.Type == "content_block_start" {
....
} else if claudeResponse.Type == "content_block_delta" {
....
} else if claudeResponse.Type == "message_delta" {
....
} else if claudeResponse.Type == "message_stop" {
return nil
} else {
return nil
}
....
}
Предупреждение PVS-Studio: V8005 [4] The ‘then’ statement is equivalent to the ‘else’ statement. relay-claude.go 474 [6]
Сложно понять, действительно ли это ошибка [7], либо же для claudeResponse.Type == "message_stop" пока попросту отсутствует логика [8].
Теперь рассмотрим срабатывание в коде tau [9] — open-source платформе для создания автономных облачных инфраструктур. Коммит 1e5036f [10].
func (p *pluginInstance) makeFunc(....) reflect.Value {
....
_out := make([]reflect.Value, len(cOut))
for idx := 0; idx < len(cOut); idx++ {
switch retTypes[idx] {
case vm.I32Type:
_out[idx] = reflect.ValueOf(int32(cOut[idx]))
case vm.I64Type: // <=
_out[idx] = reflect.ValueOf(int64(cOut[idx]))
case vm.F32Type:
_out[idx] = reflect.ValueOf(math.Float32frombits(uint32(cOut[idx])))
case vm.I64Type: // <=
_out[idx] = reflect.ValueOf(math.Float64frombits(cOut[idx]))
}
}
....
}
Предупреждение PVS-Studio: V8010 Two or more case branches have equivalent expressions. instance.go 93 [11]
Здесь мы можем наблюдать, что для разных case используется одно и то же выражение I64Type. В таком коде нет смысла, поскольку код внутри второго кейса никогда не будет выполнен. И, скорее всего, вместо второго I64Type предполагалось использовать F64Type:
switch retTypes[idx] {
case vm.I32Type:
_out[idx] = reflect.ValueOf(int32(cOut[idx]))
case vm.I64Type:
_out[idx] = reflect.ValueOf(int64(cOut[idx]))
case vm.F32Type:
_out[idx] = reflect.ValueOf(math.Float32frombits(uint32(cOut[idx])))
case vm.F64Type:
_out[idx] = reflect.ValueOf(math.Float64frombits(cOut[idx]))
}
Кстати, подобный паттерн ошибок также был найден в коде популярного блокировщика рекламы AdGuardHome [12]. Об этом мы писали в статье “Go vet не поможет. Статический анализ Golang проектов с помощью PVS-Studio [13]”.
Перейдём к прокси-серверу для AI-сервисов — Sub2API [14]. Коммит 0f03393 [15].
func cleanJSONSchemaRecursive(value any) any {
....
if hasKey(schemaMap, "properties") {
schemaMap["type"] = "object"
} else {
// 默认为 string ? or object? Gemini 通常需要明确 type
schemaMap["type"] = "object"
}
....
}
Предупреждение PVS-Studio: V8005 [4] The ‘then’ statement is equivalent to the ‘else’ statement. schema_cleaner.go 310 [16]
В этом фрагменте кода в then или else частях, возможно, есть ошибка, поскольку они одинаковые. В лучшем случае if является лишним и приводит к тому, что код тяжелее читать.
Рассмотрим другой фрагмент:
func classifyOpsPhase(errType, message, code string) string {
....
switch errType {
case "authentication_error":
return "auth"
case "billing_error", "subscription_error":
return "request" // <=
case "rate_limit_error":
if .... {
return "request"
}
return "upstream"
case "invalid_request_error":
return "request" // <=
case "upstream_error", "overloaded_error":
return "upstream"
case "api_error":
if strings.Contains(msg, opsErrNoAvailableAccounts) {
return "routing"
}
return "internal"
default:
return "internal"
}
}
Предупреждение PVS-Studio: V8009 [17] Two or more case branches perform the same actions. ops_error_logger.go 1125 [18]
Анализатор нашёл ветви конструкции switch с одинаковым телом. Срабатывания этого диагностического правила встречаются достаточно часто, поскольку для некоторых кейсов нужно иметь одинаковый исполняемый код, и некоторые не любят использовать запятую для перечисления условий.
Видим, что первый кейс, который возвращает значение request, содержит в себе несколько выражений case "billing_error", "subscription_error". И в таком случае было бы логично через запятую написать выражение case "invalid_request_error" из второго кейса, если они всегда должны приводить к одному и тому же исходу.
Однако стоит обратить внимание [19] на другой кейс:
case "rate_limit_error":
if .... {
return "request"
}
return "upstream"
Отсюда также может быть возвращено значение request, но здесь есть дополнительная обработка. Это приводит нас к мысли, что, возможно, во втором кейсе с одинаковым телом также должна быть какая-то дополнительная обработка. Но из контекста сложно понять, что именно должно быть, поэтому анализатор подсвечивает странный фрагмент кода.
Похожий случай в функции classifyopsErrorSource:
func classifyOpsErrorSource(phase string, message string) string {
// Standardized sources: client_request|upstream_http|gateway
switch phase {
case "upstream":
return "upstream_http"
case "network":
return "gateway" // <=
case "request", "auth":
return "client_request"
case "routing", "internal":
return "gateway" // <=
default:
if strings.Contains(strings.ToLower(message), "upstream") {
return "upstream_http"
}
return "gateway"
}
}
Предупреждение PVS-Studio: V8009 [17] Two or more case branches perform the same actions. ops_error_logger.go 1220 [20]
С одной стороны, возможно, это не ошибка и стоит объединить case "routing", "internal" с case "network". А с другой стороны, есть вероятность, что не хватает какого-то дополнительного кода, как в кейсе default.
PhotoPristm [21] — self-hosted менеджер фотографий с открытым исходным кодом. Проект интересен тем, что обладает встроенным AI и REST API, который позволяет агенту управлять фотобиблиотекой. Коммит 93bb435 [22].
func (c *opticsClusterer) extract() {
....
switch {
case math.Abs(d) <= c.xi:
cs = areas[j].start
ce = ue
case d > c.xi:
for k := areas[j].end; k > areas[j].end; k-- { // <=
if ....{
cs = k
break
}
}
ce = ue
default:
cs = areas[j].start
for k := i; k < e; k++ {
if ....{
ce = k
break
}
}
}
....
}
Предупреждение PVS-Studio: V8016 The loop condition will never be met. Inspect initial and final values in the ‘for’ loop. optics.go 321 [23]
Анализатор сообщает, что условие for никогда не будет выполнено, поскольку начальное и конечное значение равны areas[j].end. Скорее всего, здесь опечатка, и условие должно выглядеть следующим образом:
for k := areas[j].end; k > areas[j].start; k-- {
....
}
Ещё один прокси — axonhub [24]. Коммит bfc11e01 [25].
func AggregateStreamChunks(....) ([]byte, llm.ResponseMeta, error) {
....
if event.Delta.Thinking != nil {
if contentBlocks[index].Type == "thinking" {
if contentBlocks[index].Thinking == nil {
contentBlocks[index].Thinking = lo.ToPtr("")
}
*contentBlocks[index].Thinking += *event.Delta.Thinking
} else {
// Convert to thinking block if it's not already
contentBlocks[index].Type = "thinking"
contentBlocks[index].Thinking = event.Delta.Thinking
}
}
if event.Delta.Signature != nil { // <=
// Handle signature delta - append to thinking block signature
if contentBlocks[index].Type == "thinking" {
if event.Delta.Signature != nil { // <=
if contentBlocks[index].Signature == nil {
contentBlocks[index].Signature = event.Delta.Signature
} else {
contentBlocks[index].Signature = lo.ToPtr(....)
}
}
} else {
// Convert to thinking block if it's not already
contentBlocks[index].Type = "thinking"
contentBlocks[index].Signature = event.Delta.Signature
}
}
....
}
Предупреждение PVS-Studio: V8020 Recurring check. The ‘event.Delta.Signature != nil’ condition was already verified on line 96. aggregator.go 96 [26]
Анализатор обнаружил рекурсивную проверку event.Delta.Signature != nil. Повторная проверка бессмысленна, поскольку она всегда будет истинной.
На очереди LocalAI [27] — открытый движок для локального использования моделей искусственного интеллекта [28]. Коммит dd8e74a [29].
func (r *RunCMD) Run(ctx *cliContext.Context) error {
....
if r.DisableMetricsEndpoint {
opts = append(opts, config.DisableMetricsEndpoint)
}
if r.DisableRuntimeSettings {
opts = append(opts, config.DisableRuntimeSettings)
}
if r.EnableTracing {
opts = append(opts, config.EnableTracing)
}
if r.EnableTracing {
opts = append(opts, config.EnableTracing)
}
opts = append(opts, config.WithTracingMaxItems(r.TracingMaxItems))
....
}
Предупреждение PVS-Studio: V8017 [30] The conditions of the ‘if’ statements situated alongside each other are equivalent. Check lines: 158, 162. run.go 158 [31]
Можно заметить, что в функции Run дублируются строчки:
if r.EnableTracing {
opts = append(opts, config.EnableTracing)
}
С большой вероятностью это ошибка, которая может привести к тому, что opts = append(opts, config.EnableTracing) будет выполнена два раза, если r.EnableTracing имеет значение true.
Также возможно, что вместо второго r.EnableTracing и config.EnableTracing должно быть что-то другое, и часть функционала попросту утеряна. Скорее всего, эта ошибка допущена в результате copy-paste.
Перейдём к следующему срабатыванию:
func parseXMLWithFormat(s string, format *XMLToolCallFormat) (....) {
....
for _, match := range toolCallMatches {
if len(match) < 3 {
continue
}
....
var functionContent string
if len(match) >= 3 {
if format.ToolSep == "" && format.KeyStart != "" {
functionContent = match[2]
} else {
functionContent = match[2]
}
}
....
}
....
}
Предупреждение PVS-Studio: V8005 [4] The ‘then’ statement is equivalent to the ‘else’ statement. parse.go 902 [32]
Невооружённым глазом видно, что then и else блоки идентичны, и в таком случае нет смысла использовать if.
Скорее всего, это ошибка copy-paste, и во второй строке functionContent = match[2] забыли изменить индекс.
И сразу три срабатывания V8005 [4] было выдано на три похожих фрагмента:
func ChatEndpoint(....) echo.HandlerFunc {
....
if len(cleanedContent) > len(lastEmittedCleanedContent) &&
strings.HasPrefix(cleanedContent, lastEmittedCleanedContent) {
deltaContent = cleanedContent[len(lastEmittedCleanedContent):]
lastEmittedCleanedContent = cleanedContent
} else if cleanedContent != lastEmittedCleanedContent {
// If cleaned content changed but not in a simple append,
// extract delta from cleaned content
// This handles cases where thinking tags are removed mid-stream
if lastEmittedCleanedContent == "" {
deltaContent = cleanedContent // <=
lastEmittedCleanedContent = cleanedContent // <=
} else {
// Content changed in non-append way, use the new cleaned content
deltaContent = cleanedContent // <=
lastEmittedCleanedContent = cleanedContent // <=
}
}
....
}
Предупреждение PVS-Studio: V8005 [4] The ‘then’ statement is equivalent to the ‘else’ statement. chat.go 85
Опять then и else имеют одинаковый код. Однако в этот раз таких фрагментов три. Это наводит на мысль, что код мог быть сгенерирован, а затем растаскан по другим функциям.
На этом всё. Если вам интересны разборы проектов, предназначенных для использования AI и агентной разработки, то можете написать об этом в комментариях, и мы учтём ваше мнение :)
Напомню, что недавно в PVS-Studio появилась программа раннего доступа [1] к нашим новым анализаторам, в которой вы можете принять участие! EAP доступно для языков JavaScript, TypeScript и Go.
Если вы хотите самостоятельно проверить проект с помощью PVS-Studio, то попробовать анализатор можете по ссылке [33]!
Берегите себя и свой код!
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Georgii Tormozov. Double AI agents: What’s hiding in your Golang code [34].
Автор: GoshariqChan
Источник [35]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/30940
URLs in this post:
[1] программе раннего доступа: https://pvs-studio.ru/ru/pvs-studio-eap/
[2] new-api: https://github.com/QuantumNous/new-api
[3] 22b6b16: https://github.com/QuantumNous/new-api/tree/22b6b16702b7a9a5a751d73b2641b334050f2206
[4] V8005: https://pvs-studio.ru/ru/docs/warnings/v8005/
[5] ability.go 114: https://github.com/QuantumNous/new-api/blob/5dd0d3bcbd7b1d523bd046a5f9cf9fc8ce28d579/model/ability.go#L114
[6] relay-claude.go 474: https://github.com/QuantumNous/new-api/blob/5dd0d3bcbd7b1d523bd046a5f9cf9fc8ce28d579/relay/channel/claude/relay-claude.go#L507
[7] ошибка: http://www.braintools.ru/article/4192
[8] логика: http://www.braintools.ru/article/7640
[9] tau: https://github.com/taubyte/tau
[10] 1e5036f: https://github.com/taubyte/tau/tree/1e5036ffab19efee73a50c5b0453756995bcb7bf
[11] instance.go 93: https://github.com/taubyte/tau/blob/c080c757c12b4156cc950c26bca17d40ac950003/pkg/vm-orbit/satellite/vm/instance.go#L93
[12] AdGuardHome: https://github.com/AdguardTeam/AdGuardHome
[13] Go vet не поможет. Статический анализ Golang проектов с помощью PVS-Studio: https://pvs-studio.ru/ru/blog/posts/go/1342/#ID5543A82B01
[14] Sub2API: https://github.com/Wei-Shaw/sub2api
[15] 0f03393: https://github.com/Wei-Shaw/sub2api/tree/0f0339301022c18ec7527382d1c7edc7b2933c8f
[16] schema_cleaner.go 310: https://github.com/Wei-Shaw/sub2api/blob/f5bd25bea045e728846b38bf18080ffa48d133c6/backend/internal/pkg/antigravity/schema_cleaner.go#L310
[17] V8009: https://pvs-studio.ru/ru/docs/warnings/v8009/
[18] ops_error_logger.go 1125: https://github.com/Wei-Shaw/sub2api/blob/f5bd25bea045e728846b38bf18080ffa48d133c6/backend/internal/handler/ops_error_logger.go#L1125
[19] внимание: http://www.braintools.ru/article/7595
[20] ops_error_logger.go 1220: https://github.com/Wei-Shaw/sub2api/blob/f5bd25bea045e728846b38bf18080ffa48d133c6/backend/internal/handler/ops_error_logger.go#L1220
[21] PhotoPristm: https://github.com/photoprism/photoprism
[22] 93bb435: https://github.com/photoprism/photoprism/tree/93bb435203a9338da6e781ec8d55e7cdde1aed21
[23] optics.go 321: https://github.com/photoprism/photoprism/blob/464798234a9b7cfeb2ccda22d2f68b8103ea08ec/pkg/vector/alg/optics.go#L321
[24] axonhub: https://github.com/looplj/axonhub
[25] bfc11e01: https://github.com/looplj/axonhub/tree/bfc11e0114f47726b271c9c800f9bcaa1557bf88
[26] aggregator.go 96: https://github.com/looplj/axonhub/blob/aab5159add6b8876571e9a222ff25a73f471f7a2/llm/transformer/anthropic/aggregator.go#L96
[27] LocalAI: https://github.com/mudler/LocalAI
[28] интеллекта: http://www.braintools.ru/article/7605
[29] dd8e74a: https://github.com/mudler/LocalAI/tree/dd8e74a486df34cdd2888f1643aecef0099d3a4a
[30] V8017: https://pvs-studio.ru/ru/docs/warnings/v8017/
[31] run.go 158: https://github.com/mudler/LocalAI/blob/dd8e74a486df34cdd2888f1643aecef0099d3a4a/core/cli/run.go#L158
[32] parse.go 902: https://github.com/mudler/LocalAI/blob/dd8e74a486df34cdd2888f1643aecef0099d3a4a/pkg/functions/parse.go#L902
[33] ссылке: https://pvs-studio.ru/ru/pvs-studio/try-free/?utm_source=website&utm_medium=habr&utm_campaign=article&utm_content=1377
[34] Double AI agents: What’s hiding in your Golang code: https://pvs-studio.com/en/blog/posts/1377/
[35] Источник: https://habr.com/ru/companies/pvs-studio/articles/1041056/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1041056
Нажмите здесь для печати.