ai-forever / ai-forever/gpt2giga
API v2 parallel_tool_calls
- 主要言語
- Python
- スター
- 129
- フォーク
- 48
- PR マージ指標
- 30日以内にマージされた PR はありません
説明
API v2: при parallel_tool_calls до клиента доходит только первый вызов из ответа GigaChat
## Описание ошибки
При работе с контрактом GigaChat **v2** и включённом `parallel_tool_calls` стенд
возвращает **несколько** вызовов инструментов в одном ответе, но клиент получает
**только первый** — остальные теряются при обратной трансформации в OpenAI-формат.
Запрос gpt2giga строит корректно, флаг доезжает до стенда, стенд отвечает двумя
вызовами. Теряются они уже на обратном пути.
## Окружение
### Настройка gpt2giga
- **Версия gpt2giga**: 0.2.5a1 (коммит `f3f2d2f1`, 2026-07-26)
- **Способ установки**: из исходников (`uv run --no-sync gpt2giga`)
- **Версия Python**: 3.14.2
- **ОС**: Ubuntu 24.04
### Конфигурация GigaChat
- **Модель GigaChat**: одна из моделей, поддерживающих параллельные вызовы
- **Настройки авторизации**: `GIGACHAT_CREDENTIALS` + `GIGACHAT_AUTH_URL`
(внутренний стенд, Basic → токен)
- **Контракт API**: v2 (`GPT2GIGA_GIGACHAT_API_MODE=v2`, обращение по `/v2/...`)
```
GIGACHAT_BASE_URL=https://<стенд>/v2
GPT2GIGA_GIGACHAT_API_MODE=v2
GPT2GIGA_ENABLE_API_KEY_AUTH=False
GPT2GIGA_MODE=DEV
```
## Как воспроизвести
### Используемый метод
- [x] curl
### Тело запроса
Запрос к gpt2giga (OpenAI-формат)
```bash
curl -s http://127.0.0.1:8090/v2/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer not-used' \
-d '{
"model": "<модель>",
"parallel_tool_calls": true,
"messages": [{"role": "user", "content": "погода в Москве и курс доллара"}],
"tools": [
{"type": "function", "function": {"name": "get_weather", "description": "Погода",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}},
"required": ["city"]}}},
{"type": "function", "function": {"name": "get_rate", "description": "Курс",
"parameters": {"type": "object", "properties": {"currency": {"type": "string"}},
"required": ["currency"]}}}
]
}'
```
### Шаги для воспроизведения
1. Запустить gpt2giga с конфигурацией выше.
2. Отправить запрос по пути **`/v2/...`** с двумя независимыми инструментами и
промптом, требующим обоих.
3. В ответе клиента — один `tool_calls` вместо двух.
## Что происходит на самом деле
Мы поставили прозрачный снифер между gpt2giga и стендом и записали обе стороны
обмена. Три стадии:
**1. Запрос gpt2giga → стенд — корректный v2:**
```json
{
"model": "<модель>",
"messages": [{"content": [{"text": "погода в Москве и курс доллара"}], "role": "user"}],
"model_options": {"temperature": 0.0, "parallel_tool_calls": true},
"tools": [{"functions": {"specifications": [
{"name": "get_weather", "description": "Погода", "parameters": {...}},
{"name": "get_rate", "description": "Курс", "parameters": {...}}
]}}]
}
```
**2. Ответ стенда → gpt2giga — ДВА вызова:**
```json
{
"messages": [{
"role": "assistant",
"content": [
{"function_call": {"id": "d91478bc-…", "name": "get_weather", "arguments": {"city": "Москва"}}},
{"function_call": {"id": "d6cc4443-…", "name": "get_rate", "arguments": {"currency": "USD"}}}
]
}],
"finish_reason": "function_call"
}
```
**3. Ответ gpt2giga → клиент — ОДИН вызов:**
```json
{"choices": [{"message": {"tool_calls": [
{"function": {"name": "get_weather", "arguments": "{\"city\": \"Москва\"}"}}
]}}]}
```
Прямой запрос к стенду тем же payload'ом стабильно даёт два вызова (проверено
3 из 3), через gpt2giga — один.
## Ожидаемое поведение
Все блоки `function_call` из `content` ответа v2 должны попадать в массив
`tool_calls` OpenAI-ответа, с сохранением `id` каждого вызова — иначе клиент не
сможет вернуть результаты по идентификаторам.
## Предполагаемая причина
`packages/gpt2giga/src/gpt2giga/protocol/response/gigachat_chat_completion_adapter.py`,
функция `extract_chat_completion_function_call`:
```python
def extract_chat_completion_function_call(message_or_response: Any) -> Optional[dict]:
"""Extract the first function call from a chat completion message."""
...
for part in content:
part_data = _dump_model(part)
function_call = _normalize_function_call(part_data.get("function_call"))
if function_call:
return function_call # ← возвращается первый, цикл прерывается
return None
```
Функция по устройству возвращает ровно один вызов (что отражено и в докстроке),
а вызывается в четырёх местах — обычный ответ и стриминг.
## Дополнительно
Клиентский флаг доезжает до стенда в обеих формах: `parallel_tool_calls` в корне
запроса (OpenAI-стиль) и `model_options` целиком (проходит насквозь). На
поведение это не влияет — дефект в ответе.
コントリビューションガイド
評価
この issue はまだ評価されていません。