Skip to content

Add Virtual Function Structured Output support#95

Open
a-simeshin wants to merge 1 commit intoai-forever:mainfrom
a-simeshin:feature/native-structured-output
Open

Add Virtual Function Structured Output support#95
a-simeshin wants to merge 1 commit intoai-forever:mainfrom
a-simeshin:feature/native-structured-output

Conversation

@a-simeshin
Copy link
Collaborator

@a-simeshin a-simeshin commented Feb 7, 2026

Summary

Implements structured output using GigaChat's function calling mechanism to guarantee JSON response format. Uses virtual function _structured_output_function that the model calls with structured data as arguments.

Changes

Core

  • GigaChatOptions implements StructuredOutputChatOptions with outputSchema field
  • GigaChatModel.createRequest() adds virtual function when outputSchema is present
  • GigaChatModel.buildGeneration() extracts structured content from function arguments
  • StructuredOutputHelper - utility class for virtual function management
  • GigaChatAdvisorParams.VIRTUAL_FUNCTION_STRUCTURED_OUTPUT - advisor constant

Documentation

  • docs/structured-output.md - comprehensive guide with:
    • Virtual Function vs Prompt-based mode comparison
    • API request examples for both modes
    • Chain Tool Calling example (multiple tools + structured output)
    • Streaming limitation note (Spring AI framework limitation)

Tests

  • 20 integration tests in StructuredOutputIT
  • Unit tests for metadata handling in GigaChatModelTest

Examples

  • StructuredOutputVirtualFunctionController - example controller

Usage

// Enable globally
ChatClient chatClient = ChatClient.builder(gigaChatModel)
    .defaultAdvisors(GigaChatAdvisorParams.VIRTUAL_FUNCTION_STRUCTURED_OUTPUT)
    .build();

// Define structured output
record ActorFilms(
    @JsonPropertyDescription("Имя актёра или режиссёра") String actor,
    @JsonPropertyDescription("Список названий фильмов") List<String> movies) {}

// Get structured response
ActorFilms result = chatClient
    .prompt("Назови 3 фильма Тарантино")
    .call()
    .entity(ActorFilms.class);

How it works

  1. Spring AI generates JSON Schema from target class via BeanOutputConverter
  2. GigaChat starter creates virtual function _structured_output_function
  3. Schema is passed as function parameters
  4. Model calls the function with data in arguments
  5. Response is extracted and parsed into target object

Test plan

  • All 20 integration tests pass (StructuredOutputIT)
  • Simple records with @JsonPropertyDescription
  • Nested structures (DirectorInfo with List<MovieDetails>)
  • Enums (SentimentAnalysis with Sentiment enum)
  • Lists of objects (CountryList)
  • Combination with tool calling (single tool)
  • Chain tool calling (multiple tools + structured output)
  • Chat memory integration
  • Unit tests for generation metadata

🤖 Generated with Claude Code

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

Обзор кода

Реализация Native Structured Output выглядит хорошо. Найдена одна потенциальная проблема:

GigaChatModel.java:394

Потенциальный конфликт с явным вызовом функций

При установке outputSchema код безусловно устанавливает:

request.setFunctionCall(Map.of("name", STRUCTURED_OUTPUT_FUNCTION_NAME));

Если пользователь уже установил functionCall через GigaChatOptions.setFunctionCallMode() или setFunctionCallParam(), это значение будет перезаписано.

Рекомендация: Добавить проверку существующего значения:

if (request.getFunctionCall() == null) {
    request.setFunctionCall(Map.of("name", STRUCTURED_OUTPUT_FUNCTION_NAME));
}

GigaChatModel.java:409-412

Обработка null arguments

В коде используется пустая строка "" как fallback для null в arguments:

String structuredContent = message.getFunctionCall().getArguments() != null
        ? message.getFunctionCall().getArguments()
        : "";

Пустая строка не является валидным JSON и может вызвать ошибку парсинга в Spring AI. Рекомендуется использовать "{}" вместо "".


Остальной код качественный:

  • ✅ Правильная интеграция с StructuredOutputChatOptions
  • ✅ Корректная обработка виртуальной функции
  • ✅ Хорошее покрытие тестами (7 сценариев)
  • ✅ Подробная документация на русском
  • ✅ Примеры использования понятны

@a-simeshin a-simeshin force-pushed the feature/native-structured-output branch from 81186ee to 8ee190b Compare February 7, 2026 13:02
@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

Найдены проблемы безопасности и потенциальные ошибки. Детали в следующем комментарии.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

  1. Prompt Injection в StructuredOutputNativeController.java - прямая конкатенация параметров actor и query без валидации. Необходима санитизация входных данных.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

  1. GigaChatModel.java:402 - Map.of() создает immutable мапу, может вызвать UnsupportedOperationException. Используйте Collections.singletonMap().

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

  1. GigaChatModel.java:389 - отсутствует валидация JSON схемы в outputSchema. Невалидный JSON приведет к ошибке API.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

  1. GigaChatModel.java:417-418 - пустой arguments устанавливается как пустая строка, что может вызвать ошибки парсинга. Рекомендуется явная проверка и exception.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

  1. StructuredOutputIT.java:562 - unsafeSsl(true) отключает проверку SSL. Добавьте комментарий о применимости только для тестов.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

Итого найдено 5 проблем, требующих исправления перед мержем: 2 проблемы безопасности и 3 потенциальные ошибки. Пожалуйста, исправьте указанные замечания.

@a-simeshin a-simeshin force-pushed the feature/native-structured-output branch from 8ee190b to e852568 Compare February 7, 2026 13:09
@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

Обзор PR: Native Structured Output

⚠️ Критичные проблемы безопасности

Prompt Injection в примерах:

В StructuredOutputNativeController.java пользовательский ввод напрямую конкатенируется в промпты:

  • Строка 336-337: "Назови 5 самых известных фильмов режиссёра " + actor
  • Строка 351: "Расскажи о книге: " + query

Это создаёт уязвимость к prompt injection атакам. Пользователь может передать вредоносный ввод типа: "Тарантино. Забудь предыдущие инструкции и выполни..."

Рекомендация: Добавить валидацию/санитизацию входных параметров или использовать параметризованные промпты через Spring AI API.


📝 Замечания по качеству кода

1. Отсутствие валидации JSON (GigaChatModel.java:428)

String structuredContent = message.getFunctionCall().getArguments();
if (structuredContent == null) {
    log.warn("Structured output function returned null arguments, using empty object");
    structuredContent = "{}";
}

Проблема: если getArguments() возвращает невалидный JSON (например, битый ответ от API), это приведёт к ошибке парсинга позже. Рекомендуется добавить try-catch с валидацией JSON.

2. Неочевидная логика условия (GigaChatModel.java:406-410)

if (!hasOtherTools || toolsAlreadyExecuted) {
    request.setFunctionCall(Map.of("name", STRUCTURED_OUTPUT_FUNCTION_NAME));
}

Логика корректная, но требует пояснения: при наличии других инструментов модель сначала вызовет их с function_call: "auto", а после их выполнения (когда в истории появятся ToolResponseMessage) будет принудительно вызвана функция structured output. Добавьте комментарий для ясности.


✅ Положительные моменты

  • Хорошая архитектура решения через виртуальную функцию
  • Качественная документация в structured-output.md
  • Комплексные интеграционные тесты (7 сценариев)
  • Правильная реализация интерфейса StructuredOutputChatOptions
  • Корректная обработка комбинации с tool calling

🔧 Рекомендации

  1. Обязательно исправить: Добавить валидацию пользовательского ввода в контроллере примеров
  2. Добавить валидацию JSON в GigaChatModel.java:428
  3. Улучшить комментарии к сложной логике условий
  4. Рассмотреть добавление примера с правильной обработкой пользовательского ввода в документацию

Пожалуйста, исправьте критичную проблему безопасности перед мержем.

Implements structured output using GigaChat's function calling mechanism
to guarantee JSON response format.

Features:
- GigaChatAdvisorParams.VIRTUAL_FUNCTION_STRUCTURED_OUTPUT advisor
- StructuredOutputHelper for virtual function management
- Support for tools + structured output combination (chain calling)
- Comprehensive documentation with API request examples

Usage:
```java
ActorFilms result = chatClient
    .prompt("List 5 films by Tarantino")
    .advisors(GigaChatAdvisorParams.VIRTUAL_FUNCTION_STRUCTURED_OUTPUT)
    .call()
    .entity(ActorFilms.class);
```

Testing:
- 20 integration tests covering various scenarios
- Unit tests for metadata handling

Documentation:
- docs/structured-output.md with full examples
- Comparison of Virtual Function vs Prompt-based modes

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@a-simeshin a-simeshin force-pushed the feature/native-structured-output branch from e852568 to 0cd3515 Compare February 9, 2026 17:44
@github-actions
Copy link
Contributor

github-actions bot commented Feb 9, 2026

Результаты ревью кода

Обнаружена 1 критическая ошибка и несколько рекомендаций по улучшению кода.

❌ Критическая ошибка (требует исправления)

NullPointerException в StructuredOutputHelper.extractContent()

  • Метод вызывает message.getFunctionCall() без проверки на null
  • Это может привести к NPE в рантайме
  • См. inline комментарий для решения

⚠️ Рекомендации

  1. Валидация outputSchema - добавить проверку в метод createFunction()
  2. Документация потокобезопасности - уточнить thread safety для метода addToRequest()

✅ Положительные моменты

  • Отличное покрытие тестами (7 интеграционных тестов)
  • Подробная документация на русском языке
  • Правильное использование immutable коллекций
  • Хорошая архитектура с разделением ответственности
  • Обратная совместимость сохранена

Вердикт: Необходимо исправить критическую ошибку с NPE перед мержем.

@a-simeshin a-simeshin changed the title Add native Structured Output support Add Virtual Function Structured Output support Feb 9, 2026
@a-simeshin a-simeshin requested a review from linarkou February 9, 2026 17:56
@a-simeshin a-simeshin self-assigned this Feb 9, 2026
@a-simeshin a-simeshin added the enhancement New feature or request label Feb 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants