PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.3.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.3.1
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / AI / Services / ResponseValidator.php

ResponseValidator.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.1, at includes/AI/Services/ResponseValidator.php

96 lines 3.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\AI\Services;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 /**
10 * Turns a raw provider chat result into a clean list of candidate operations.
11 *
12 * Primary path: the provider returned structured tool calls. Fallback path: some
13 * models describe operations as a JSON block inside the text reply — we recover
14 * those too. Everything returned here is still *unvalidated* and must pass
15 * {@see \Better_Payment\Lite\AI\Operations\OperationValidator} before use.
16 */
17 class ResponseValidator {
18
19 /**
20 * Extract candidate operations (wire shape: [ 'op' => name, ...args ]).
21 *
22 * @param array $chat_result Normalised provider result (text, tool_calls, ...).
23 * @return array<int, array>
24 */
25 public static function extract_operations( array $chat_result ): array {
26 $operations = [];
27
28 foreach ( (array) ( $chat_result['tool_calls'] ?? [] ) as $call ) {
29 $name = $call['name'] ?? '';
30 if ( '' === $name ) {
31 continue;
32 }
33 $args = is_array( $call['arguments'] ?? null ) ? $call['arguments'] : [];
34 $operations[] = array_merge( [ 'op' => $name ], $args );
35 }
36
37 // Fallback: recover a JSON operations block from the text reply.
38 if ( empty( $operations ) ) {
39 $operations = self::recover_from_text( (string) ( $chat_result['text'] ?? '' ) );
40 }
41
42 return $operations;
43 }
44
45 /**
46 * Best-effort recovery of an operations array embedded in free text.
47 *
48 * Looks for a fenced ```json block or a bare JSON object/array containing an
49 * `operations` key or a top-level array of operation objects.
50 *
51 * @return array<int, array>
52 */
53 public static function recover_from_text( string $text ): array {
54 if ( '' === trim( $text ) ) {
55 return [];
56 }
57
58 $candidates = [];
59
60 // Fenced code block(s).
61 if ( preg_match_all( '/```(?:json)?\s*(.+?)```/s', $text, $matches ) ) {
62 foreach ( $matches[1] as $block ) {
63 $candidates[] = $block;
64 }
65 }
66 // The whole text as a last resort.
67 $candidates[] = $text;
68
69 foreach ( $candidates as $candidate ) {
70 $decoded = json_decode( trim( $candidate ), true );
71 if ( ! is_array( $decoded ) ) {
72 continue;
73 }
74 if ( isset( $decoded['operations'] ) && is_array( $decoded['operations'] ) ) {
75 return array_values( array_filter( $decoded['operations'], 'is_array' ) );
76 }
77 // A bare list of operation objects.
78 if ( self::looks_like_op_list( $decoded ) ) {
79 return array_values( array_filter( $decoded, 'is_array' ) );
80 }
81 }
82
83 return [];
84 }
85
86 /**
87 * @param array $value
88 */
89 private static function looks_like_op_list( array $value ): bool {
90 if ( empty( $value ) || ! isset( $value[0] ) || ! is_array( $value[0] ) ) {
91 return false;
92 }
93 return isset( $value[0]['op'] ) || isset( $value[0]['operation'] ) || isset( $value[0]['type'] );
94 }
95 }
96