Problem/Motivation

We right now rely on non-structured response with AI prompts and want to switch to structured response.

Caveat that OpenAI implementation is different from Anthropic and maybe also Gemini. The Drupal AI module version might have have a generic way to handle structure responses so we might want to wait for that.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

dmundra created an issue. See original summary.

dmundra’s picture

Issue summary: View changes
dmundra’s picture

Assigned: Unassigned » dmundra
dmundra’s picture

So I started looking at src/Normalizer/jsonapi/AiMigrationSchemaNormalizer.php as a way to format the schema to the OpenAI format in the documentation.

I was able to add name, strict, and schema and wrap the schema in into that level. Here the first error I got after getting to a point where it accepted those values,

[error] Error invoking model response: Invalid schema for response_format 'ai-migration-schema': In context=('properties', 'data', 'properties', 'attributes', 'properties', 'field_post_content'), 'additionalProperties' is required to be supplied and to be false.

So it appears that every object schemata creates will need to have additionalProperties added. So going to try that next.

dmundra’s picture

Got this working today after doing some schema manipulation like:

  • Inject additionalProperties: false for all objects
  • Setting all required fields for attributes and relationships. Was the easiest then trying to do it nullable like in this post
  • Moved the main json schema to be nested
dmundra’s picture

QA steps to try both structured output for OpenAI, Anthropic, and Gemini but also structured output not set for the same AI.

Initial configuration:

  1. Ensure AI logging module is enabled
  2. Go to /admin/config/ai/logging/settings
  3. Turn on Automatically log requests
  4. Roll back any migrated content: ddev drush migrate:rollback simple_content_migration
  5. Clear cache: ddev drush cr
  6. Log in as an admin.

Steps for testing with structured_output set to true:

  1. Update modules/ai_migration_example/migrations/simple_content_migration.yml, change lines 16-22 to:
          provider_id: openai
          model_id: gpt-5.1
          structured_output: true
  2. Import with OpenAI: ddev drush migrate:import simple_content_migration
  3. Confirm 2 nodes import with no issues.
  4. Go to /admin/config/ai/logging/collection and view the last log ran to confirm that chat_structured_json_schema was being set.
  5. Roll back: ddev drush migrate:rollback simple_content_migration
  6. Clear cache: ddev drush cr
  7. Update modules/ai_migration_example/migrations/simple_content_migration.yml, change line 16-23 back to anthropic like below and set structured_output to true:
          provider_id: anthropic
          model_id: claude-sonnet-4-5-20250929
          config:
            temperature: 0
            top_k: 0
            max_tokens: 8096
          structured_output: true
  8. Clear cache: ddev drush cr
  9. Run the import with Anthropic: ddev drush migrate:import simple_content_migration
  10. Confirm 2 nodes import with no issues.
  11. Go to /admin/config/ai/logging/collection and view the last log ran to confirm that chat_structured_json_schema was being set.
  12. Roll back: ddev drush migrate:rollback simple_content_migration
  13. Clear cache: ddev drush cr
  14. Update modules/ai_migration_example/migrations/simple_content_migration.yml, change line 16-23 to Gemini like below and set structured_output to true:
          provider_id: gemini
          model_id: gemini-2.5-flash
          config:
            temperature: 0
            topP: 0
            topK: 0
            maxOutputTokens: 32384
          structured_output: true
  15. Clear cache: ddev drush cr
  16. Run the import with Gemini: ddev drush migrate:import simple_content_migration
  17. Confirm 2 nodes import with no issues.
  18. Go to /admin/config/ai/logging/collection and view the last log ran to confirm that chat_structured_json_schema was being set.
  19. Roll back: ddev drush migrate:rollback simple_content_migration
  20. Clear cache: ddev drush cr

Repeat the above but change structured_output line to false. Confirm the schema does continue to import when it sends it via system prompt.

Repeat both for complex_content_migration. Confirm the complex migration works with both structured_output and system prompt.

dmundra’s picture

Status: Active » Needs review

Testing structured_output true for simple_content_migration with my changes worked for OpenAI (gpt-5.1), Anthropic (claude-sonnet-4-5-20250929), and Gemini (gemini-2.5-flash). Albeit everytime I rolled back I got rollback error

Failed to log error: TypeError: Drupal\Core\Database\StatementWrapperIterator::__construct(): Argument #2 ($clientConnection) must be of type object, null given, called in /var/www/html/web/core/lib/Drupal/Core/Database/Connection.php on line 441 in Drupal\Core\Database\StatementWrapperIterator->__construct() (line 38 of /var/www/html/web/core/lib/Drupal/Core/Database/StatementWrapperIterator.php). #0 /var/www/html/web/core/lib/Drupal/Core/Database/Connection.php(441): Drupal\Core\Database\StatementWrapperIterator->__construct()

Testing structured_output false for simple_content_migration worked for all models as well.

Only thing remaining is trying complex content migration. Additionally in the review, need to look at the error above and the mess of changes to the schema. I am now including more stuff in the schema and the AI is generating values for it which the migration is so far ignoring or probably a cause of the above error.

dmundra’s picture

dmundra’s picture

Status: Needs review » Needs work

Testing this with complex_content_migration revealed a flaw which is trying to generate schema of an entity that references a child schema will generate the AI schema version for those child ones as well. So ya we need to figure out how to correctly handle recursive entity schema generation.

dmundra’s picture

dmundra’s picture

To dos:
* Revert all code in https://git.drupalcode.org/project/ai_migration/-/merge_requests/33
* Add another normalizer that take the generated schema from schemata to generate one for the AI schema. Try to do that in the current normalizer to handle the recursive case of entity referencing entities.
* Look for normalizer example for this AI schema in Symfony AI, Drupal AI module maybe other places that we can plug in.

dmundra’s picture

From kducharm

Structured Output/Response: we discussed if when Daniel is back + Jonathan that we could review if we might be able to inject some of the response JSON schema validation (as in if it's valid or not, not actually trying to fix/adjust it) in rather than having to put a lot of hours into the provider's support right now - since there seems to be a potential shift to Symfony AI at some point, should we invest a lot of time working  on providers versus just knowing when the AI prompt isn't respecting the response schema we told it to return and adjusting our prompts.