Skip to content

6.0.0

Choose a tag to compare

@pocketarc pocketarc released this 27 Sep 17:46
4905cd0

What's Changed

  • fix!: Replay structured requests with their original schema and count the tokens of every attempt. by @pocketarc in #14
  • fix: Return the response when a cache write fails. by @pocketarc in #15

ai_workflow_requests has a new schema_name column

The nullable column holds the schema name that each structured request was sent with. AiWorkflowReplayer uses it to replay the request with the same schema name. For requests logged before the migration, which have no schema_name, AiWorkflowReplayer still uses the name schema.

If ai-workflow.logging.enabled is true, AI calls fail with a database error until the migration has run. Run the migration before the upgraded app handles any AI calls.

Copy the new migration into database/migrations with the current timestamp as its filename prefix. Then run the migration:

cp vendor/pocketarc/ai-workflow/database/migrations/0001_01_01_000010_add_schema_name_to_ai_workflow_requests.php \
   "database/migrations/$(date +%Y_%m_%d_%H%M%S)_add_schema_name_to_ai_workflow_requests.php"
php artisan migrate

With the current timestamp, the new migration runs after the ai-workflow migrations that you published before. If those filenames start with the date on which you published them, the original 0001_01_01_000010 prefix would make the new migration run before the migration that creates ai_workflow_requests, so migrate:fresh would fail.

Do not run vendor:publish --tag=ai-workflow-migrations for this upgrade. With Laravel's default database.migrations.update_date_on_publish setting, the command publishes every ai-workflow migration again with a new timestamp. migrate then fails on the first copy, which creates tables that already exist.

sendStructuredData() wraps request errors in StructuredDataRequestException

sendStructuredData() now throws StructuredDataRequestException when an attempt fails with an exception that does not extend AiWorkflowException, such as a provider error, a rate-limit error or a connection failure. getPrevious() returns the original exception, and getCode() returns its code, or 0 if that code is not an integer. The attempts property holds the number of the attempt that failed. usage() returns the tokens of the earlier attempts plus those of the failed attempt's response, if one was received. An exception that extends AiWorkflowException, such as GuardrailViolationException, is rethrown as the same instance.

If you catch Prism or laravel-integrations exceptions around sendStructuredData(), catch StructuredDataRequestException instead and check getPrevious(). The other AiService methods still throw the original exceptions.

Other behaviour changes

  • StructuredDataResult->usage now holds the tokens of every attempt, including attempts that failed validation. No tokens are counted for an attempt served from the cache.
  • Every exception that extends AiWorkflowException has a usage() method. It returns the tokens of the responses received before the exception was thrown, or null if no response was received. For an exception thrown by sendStructuredData(), usage() is never null and covers every attempt.
  • When a middleware such as an output guardrail rejects a response, or the finish reason is Unknown, Error or Other, the row logged for the failed request now contains that response (the text or structured output and the finish reason) and its tokens.
  • When the structured output cannot be decoded and the prompt has a fallback_model, sendStructuredMessages() and sendStructuredMessagesWithTools() now log the failed primary request and dispatch AiWorkflowRequestFailed for it. They do the same for the fallback request if it fails. In sendStructuredMessages(), the fallback request now runs through the middleware, and its response is cached.
  • If an AiWorkflowRequestCompleted listener throws an exception after a successful request, the request is no longer logged a second time as failed, and AiWorkflowRequestFailed is no longer dispatched.
  • If a cache write fails, AiService passes the exception to Laravel's report() and still returns the response. Before, AiService threw the exception, so the caller lost the response, the request was logged a second time as failed, and AiWorkflowRequestFailed was dispatched.
  • sendStructuredData() no longer caches a response that fails validation.
  • A response is now cached under a key built from the request as it was before middleware ran, which is the key used for cache reads. Before, a request was never served from the cache if a middleware had rewritten its messages or system prompt.
  • AiWorkflowReplayer now rebuilds the recorded schema exactly, including anyOf. On MySQL, which reorders JSON object keys, it restores each object's property order from its required list when that list names every property.

Full Changelog: 5.1.0...6.0.0