phore / ai-harness
Requires
- php: >=8.5
- ext-curl: *
- ext-json: *
- ext-yaml: *
- phore/json-patch: dev-main
- phore/schema: dev-main
Requires (Dev)
- phpunit/phpunit: ^13.2.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-11 21:05:53 UTC
README
Template Repository for phore library projects
Git Submodules
Beim Klonen direkt mit auschecken:
git clone --recurse-submodules <repo-url>
Nachträglich initialisieren oder aktualisieren:
git submodule update --init --recursive git submodule update --remote --merge
Reasoning options
All phore_ai_* helper functions send ['effort' => 'low'] by default.
Override the Responses API reasoning settings through the shared options:
$result = phore_ai_text('Explain the status.', [ 'reasoning' => ['effort' => 'medium'], ]); $ai = (new \Phore\AiHarness\PhoreAi()) ->withReasoning(['effort' => 'high']);
withReasoning() clones the facade. Settings also apply to streaming, image,
structured-output and callback follow-up requests. Use 'reasoning' => null
(or withReasoning(null)) to omit the parameter for models that do not support
reasoning. The array is passed through unchanged; supported fields and effort
values depend on the selected model. See the
OpenAI reasoning guide.
Optional debug logging
$result = phore_ai_text('Explain the status.', ['debug_log' => true]); $ai = (new \Phore\AiHarness\PhoreAi()) ->withLogger(new \Phore\AiHarness\Logging\ConsoleLogger()) ->withModel('gpt-5-mini');
debug_log is supported by phore_ai_text, phore_ai_struct,
phore_ai_struct_array, phore_ai_image, and phore_ai_edit_file. It accepts
false (the default), true (console output on STDERR), or a
Phore\AiHarness\Logging\LoggerInterface implementation. Invalid values,
including null, throw InvalidArgumentException before a request is sent.
withLogger() clones the facade; withLogger(null) disables logging again.
Debug text and structured-output requests stream. Every console line starts
with [model]; partial lines are flushed at response end. Images remain
non-streaming but produce lifecycle and statistics events. Return values and
STDOUT are unaffected. Warnings are red only on a terminal that supports color;
NO_COLOR disables color. Custom loggers implement log(LogEvent $event) and
receive sanitized events (run_start, request_start, text_delta,
response_end, tool_start, tool_result, tool_end, warning, stats).
Text events are buffered through complete lines before redaction. runId
identifies each invocation; the final event contains an immutable
RunStatistics object. Logger exceptions are isolated from the AI operation.
As in the current tool-error contract, only an explicit RecoverableToolException
produces an ok: false tool output so the model can correct its call. Logging
never changes which callback errors are recoverable. Five
callback rounds are the hard limit (PhoreAi::MAX_CALLBACK_ROUNDS); unresolved
calls after that limit throw. All other exceptions, including argument decoding/binding errors,
TaskErrorException, transport/authentication failures, internal PHP errors and
result-serialization failures, propagate unchanged without retries. Each failed callback counts as one error; one follow-up request
counts as one retry even if multiple calls failed in that round. With logging
disabled, the previous non-streaming and immediate-error behavior is preserved.
Tool arguments are redacted recursively; invalid argument JSON is omitted.
Results are logged by byte count, and raw exception messages, request headers
and stack traces are omitted. Recognized credentials in model text are redacted
only in logs. A single final summary includes status, requests, tool calls,
errors, retries, available token totals and monotonic total/API/tool durations
in seconds, including failed runs and structured-output hydration. Missing
usage is null (n/a in console output); when only some responses report usage,
totals sum those available values.
Global usage and estimated cost
All OpenAiClient instances automatically accumulate process-local usage,
independently of debug logging, including facade/helper calls, callback follow-ups,
streaming, images and AiRequestSpooler. No setup is required.
phore_ai_text('First task'); phore_ai_text('Second task', ['model' => 'gpt-5-nano']); $stats = get_ai_usage_stats(); printf( "%d requests, %d errors, %d input / %d output / %d total tokens; approx. %s USD\n", $stats['requests'], $stats['errors'], $stats['inputTokens'], $stats['outputTokens'], $stats['totalTokens'], $stats['totalCostUsd'] === null ? 'unknown (partial usage/prices)' : number_format($stats['totalCostUsd'], 6), ); print_r($stats['models']); // Same counters and cost fields, keyed by response model.
requests counts client request attempts (including setup failures), not facade
runs or individual tools; every callback follow-up is another request.
errors counts failed client attempts, HTTP failures, failed/incomplete/cancelled
response statuses and stream callback aborts, once per request. Errors in local
tools, hydration or spooler result callbacks after a successful API response
are not API errors. pendingRequests reports attempts still in progress.
Tokens are summed only from reported usage. Cached input and reasoning output
are subsets, not added again to totals. The response model takes precedence;
the requested model is the fallback. Missing usage increments
missingUsageRequests; only absent/blank model names remain unpricedRequests.
Unknown named models receive a conservative fallback. fallbackRequests counts
these requests; each model row includes pricingSource and
pricesPerMillionTokensUsd. knownCostUsd includes fallback estimates.
knownCostUsd always contains the priced subtotal. totalCostUsd is null
when pending requests, missing usage or unknown prices prevent a complete estimate.
Reading stats returns a detached snapshot and never clears the counters.
State lasts for the PHP runtime (one request in typical PHP-FPM, whole execution
in CLI/long-lived workers); separate processes are not combined.
Phore\AiHarness\Usage\CostEstimator uses a price snapshot checked on 2026-09-11.
Sources: OpenAI pricing,
GPT-5.5,
GPT-5.5 Pro,
GPT-5.4,
Mini,
Nano,
GPT-5.2,
GPT-4.1 and
GPT-5.
| Model | Input USD/1M | Cached input USD/1M | Output USD/1M |
|---|---|---|---|
| chat-latest | 5 | 0.5 | 30 |
| gpt-4.1 | 2 | 0.5 | 8 |
| gpt-5.2 | 1.75 | 0.175 | 14 |
| gpt-5.3-codex | 1.75 | 0.175 | 14 |
| gpt-5.4 | 2.5 | 0.25 | 15 |
| gpt-5.4-mini | 0.75 | 0.075 | 4.5 |
| gpt-5.4-nano | 0.2 | 0.02 | 1.25 |
| gpt-5.5 | 10 | 1 | 45 |
| gpt-5.5-pro | 30 | no discount | 180 |
| gpt-5.6-cyber | 12.5 | 1.25 | 75 |
| gpt-5.6-luna | 0.4 | 0.04 | 1.8 |
| gpt-5.6-sol | 8 | 0.8 | 30 |
| gpt-5.6-terra | 4 | 0.4 | 18 |
| gpt-6-astra | 20 | 2 | 75 |
| gpt-5 | 1.25 | 0.125 | 10 |
| gpt-5-mini | 0.25 | 0.025 | 2 |
| gpt-5-nano | 0.05 | 0.005 | 0.40 |
GPT-6 Astra, GPT-5.6 Sol/Terra/Luna and GPT-5.5 deliberately use their higher long-context standard rates even for short requests. Other named entries use published standard rates. These are budgeting estimates, not exact billing.
Resolution order: explicit model/override, dated snapshot, then case-insensitive
regex matching of complete name segments separated by - _ . / :.
Unknown mini models use the highest input/output rates among listed mini models
(currently 0.75 / 4.50); unknown nano models use 0.20 / 1.25.
Premium segments (pro|max|ultra|opus|astra) take precedence over mini/nano.
All other unknown names, including future higher model versions, use at least
40 / 180 USD per million input/output tokens: the component-wise upper envelope
of GPT-6 Astra long-context Fast input and GPT-5.5 Pro output.
Fallbacks assume no cache discount and can rise with higher custom rates;
lower overrides never reduce the built-in fallback floor. Exact overrides still win.
pricingSource is exact, snapshot, mini-fallback, nano-fallback,
highest-fallback or unknown; exact describes name matching, not invoice accuracy.
Pass overrides in USD per million tokens to price additional models or use updated/custom rates; the resulting snapshot is recalculated from accumulated tokens:
$estimator = new \Phore\AiHarness\Usage\CostEstimator([ 'my-model' => ['input' => 1.0, 'cachedInput' => 0.1, 'output' => 5.0], ]); $stats = get_ai_usage_stats($estimator);
These are rough token-cost estimates, not invoices: hosted tool fees, separate image/audio generation charges, storage, taxes, service tiers and long-context surcharges beyond the conservative rates above are excluded. No network pricing lookup or automatic console output takes place. Only counters are retained, not prompts or response history.