130 lines
6.4 KiB
PHP
Executable File
130 lines
6.4 KiB
PHP
Executable File
<?php
|
||
|
||
/**
|
||
* 处方库与诊单 AI 报告(Dify / OpenAI-compatible)
|
||
*
|
||
* qwen/openai 分别使用各自的服务端凭据。凭据禁止下发到客户端或写入日志。
|
||
*/
|
||
return [
|
||
'enable' => filter_var(
|
||
env('prescription_ai.ENABLE', env('prescription_ai.enable', false)),
|
||
FILTER_VALIDATE_BOOLEAN
|
||
),
|
||
'base_url' => rtrim((string) env(
|
||
'prescription_ai.BASE_URL',
|
||
env('prescription_ai.base_url', '')
|
||
), '/'),
|
||
'timeout' => (int) env(
|
||
'prescription_ai.TIMEOUT',
|
||
env('prescription_ai.timeout', 90)
|
||
),
|
||
// Clinical source files are mandatory: upload their bytes first instead of
|
||
// relying on the model server to reach the workstation's resource URLs.
|
||
'required_attachment_transport' => 'upload',
|
||
// A byte budget for additional evidence, not a claim about provider token capacity.
|
||
'required_attachment_input_bytes' => (int) env('prescription_ai.ATTACHMENT_INPUT_BYTES', 196608),
|
||
// Independent local text recognition supplements the unchanged image/model
|
||
// transport. Install the pinned offline worker before enabling on a host.
|
||
'required_attachment_ocr' => [
|
||
'enabled' => filter_var(env('prescription_ai.LOCAL_OCR_ENABLED', false), FILTER_VALIDATE_BOOLEAN),
|
||
'python' => (string) env('prescription_ai.LOCAL_OCR_PYTHON', ''),
|
||
'timeout_seconds' => (int) env('prescription_ai.LOCAL_OCR_TIMEOUT', 15),
|
||
'max_text_bytes' => 48000,
|
||
'threads' => 2,
|
||
'detection_limit' => 736,
|
||
],
|
||
'required_attachment_upload' => [
|
||
// Explicit aliases for this deployment's upload directory. Do not derive
|
||
// local-file access from an untrusted request Host or attachment URL.
|
||
'local_origins' => array_values(array_filter(array_map('trim', explode(',', (string) env(
|
||
'prescription_ai.REQUIRED_ATTACHMENT_LOCAL_ORIGINS',
|
||
'https://admin.zhenyangtang.com.cn,https://css.zhenyangtang.com.cn,http://admin.zhenyangtang.com.cn,http://css.zhenyangtang.com.cn'
|
||
))))),
|
||
'max_file_bytes' => (int) env('prescription_ai.REQUIRED_ATTACHMENT_MAX_BYTES', 20 * 1024 * 1024),
|
||
// Optional trusted server CA bundle for PHP installations without a system
|
||
// cURL certificate store; peer and hostname verification remain enabled.
|
||
'ca_file' => (string) env('prescription_ai.REQUIRED_ATTACHMENT_CA_FILE', ''),
|
||
// Optional server-owned DNS pins for remote attachment storage only. Exact
|
||
// hostname => nonempty public-IP array; origins/TLS checks still apply.
|
||
// Keep invalid JSON/root types as null so the uploader fails closed.
|
||
'resolved_hosts' => (static function () {
|
||
$decoded = json_decode((string) env('prescription_ai.REQUIRED_ATTACHMENT_RESOLVED_HOSTS', '{}'));
|
||
return $decoded instanceof \stdClass ? get_object_vars($decoded) : null;
|
||
})(),
|
||
],
|
||
// Reuse only evidence for an identical, freshly authorized source; 0 disables.
|
||
'context_compaction_cache_ttl' => (int) env('prescription_ai.CONTEXT_COMPACTION_CACHE_TTL', 300),
|
||
/**
|
||
* 单个应用请求的附件上限(默认 3),各模型可在 models 中覆盖。
|
||
* required_files 超限时在同应用分批提取全部可用附件,再结合完整正文综合;
|
||
* 确认缺失明确跳过,明确拒收逐项记录,鉴权/网络等不明确错误仍安全失败。
|
||
* 普通可选附件保留原清单策略。修改上游后核对 /parameters 的
|
||
* file_upload.number_limits 并同步此配置,避免不必要的分批。
|
||
*/
|
||
'max_files' => (int) env(
|
||
'prescription_ai.MAX_FILES',
|
||
env('prescription_ai.max_files', 3)
|
||
),
|
||
// Per-model staged analysis limits. Oversized semantic units and exhausted call budgets
|
||
// are explicit task errors; they never silently remove patient evidence or attachments.
|
||
'manual_analysis' => [
|
||
/**
|
||
* 单次提示词的字节上限(UTF-8 字节是 token 的保守上界)。调大可减少分片与压缩
|
||
* 轮次、显著缩短一份报告的总耗时;上游应用限制更严时会返回可重试的拒绝,
|
||
* 此时调小本值。
|
||
*/
|
||
'input_token_budget' => (int) env(
|
||
'prescription_ai.MANUAL_INPUT_TOKEN_BUDGET',
|
||
env('prescription_ai.manual_input_token_budget', 48000)
|
||
),
|
||
'max_calls_per_model' => (int) env(
|
||
'prescription_ai.MANUAL_MAX_CALLS_PER_MODEL',
|
||
env('prescription_ai.manual_max_calls_per_model', 128)
|
||
),
|
||
/**
|
||
* 医学研究对照模式:无论资料是否完整,两个模型都必须先各自独立开出候选处方,
|
||
* 再由服务端与人工方比较。缺口、假设与复核要求写入候选方的说明与风险提示,
|
||
* 不再以资料不足为由返回空方案。候选方仍不写回正式处方、审核或订单。
|
||
*/
|
||
'require_candidate' => filter_var(
|
||
env('prescription_ai.MANUAL_REQUIRE_CANDIDATE', true),
|
||
FILTER_VALIDATE_BOOLEAN
|
||
),
|
||
/**
|
||
* 后台分阶段分析的单次请求超时(秒,上限 300)。同步页面仍使用上面的 timeout;
|
||
* 该值必须明显小于任务租约 prescription_analysis.lease_seconds。
|
||
*/
|
||
'request_timeout' => (int) env(
|
||
'prescription_ai.MANUAL_REQUEST_TIMEOUT',
|
||
240
|
||
),
|
||
// 模型仍拒绝开方时,携带其拒绝理由重新追问的次数。
|
||
'candidate_insist_rounds' => (int) env(
|
||
'prescription_ai.MANUAL_CANDIDATE_INSIST_ROUNDS',
|
||
2
|
||
),
|
||
],
|
||
'models' => [
|
||
'qwen' => [
|
||
'name' => 'qwen3.6-35b',
|
||
'label' => '千问',
|
||
// 2026-10-23 已核对原应用发布后的 file_upload.number_limits = 10。
|
||
'max_files' => (int) env('prescription_ai.QWEN_MAX_FILES', 10),
|
||
'api_key' => (string) env(
|
||
'prescription_ai.QWEN_API_KEY',
|
||
env('prescription_ai.qwen_api_key', '')
|
||
),
|
||
],
|
||
'openai' => [
|
||
'name' => 'gpt-5.6-sol',
|
||
'label' => 'OpenAI',
|
||
// 该应用 file_upload.number_limits = 10:一次多带附件可显著减少往返次数
|
||
'max_files' => (int) env('prescription_ai.OPENAI_MAX_FILES', 10),
|
||
'api_key' => (string) env(
|
||
'prescription_ai.OPENAI_API_KEY',
|
||
env('prescription_ai.openai_api_key', '')
|
||
),
|
||
],
|
||
],
|
||
];
|