Files

1 line
162 KiB
JSON

{"repo": "azhao1981/aigis", "files": {"configs/config.yaml": {"68a70c65c0df5c5e3620e31126922eef0f29d95c": {"t": 1785745230.047601, "n": 13885, "h": "89c656f58e312a18", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n # 审计文件滚动:默认关闭(单文件,交给 logrotate)。与 log.rotate 同一套约定,\n # 切勿同时用 logrotate + 内置滚动管同一文件。/admin/audit 只查当前文件,\n # 滚动出去的历史文件是保留策略,不进查询面。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n # max_size_mb: 100 # rotate when the file exceeds this size\n # max_backups: 7 # keep at most this many rotated files\n # max_age_days: 30 # delete rotated files older than this\n # compress: true # gzip rotated files\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Response cache (non-streaming only). Identical requests within ttl_sec return\n# the previous response (X-Cache: HIT) instead of hitting the upstream. 0 =\n# disabled (default). Best for deterministic / low-sensitivity workloads — the\n# cache holds plaintext responses in memory, so keep ttl short.\ncache:\n ttl_sec: 0\n max_entries: 1000\n\n# Upstream retry (all routes). On a TRANSIENT failure — a network error, HTTP 429,\n# or any 5xx — the gateway retries with a linear backoff (backoff_ms * attempt). A\n# 4xx client error is returned immediately (a retry won't fix it). Disabled by\n# default: max_attempts <= 1 means a single try (no retry).\nretry:\n max_attempts: 1 # total tries incl. the first; set >1 to enable retry\n backoff_ms: 200 # base backoff; grows linearly per attempt\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n # token_envs — optional multi-key load balancing: list several env var\n # names and the gateway round-robins across the keys that resolve to a\n # non-empty value (env vars that are empty/unset are skipped). When set,\n # token_envs takes precedence over token_env above. Keys live only in the\n # environment, never in this file.\n # token_envs: [\"AIGIS_OPENAI_API_KEY_1\", \"AIGIS_OPENAI_API_KEY_2\"]\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n # config.rules — comma-separated rule names to apply on THIS route\n # (e.g. \"Email,Mobile Phone\"); omit/empty = all rules. Built-in names:\n # Private Key / AWS Access Key / OpenAI API Key / GitHub Token /\n # Google API Key / Email / Mobile Phone (+ any custom_rules names).\n config: {} # Uses default patterns (all rules, email: full)\n # Inbound guards (optional). Both are request-side transforms; a hit\n # aborts the request BEFORE it reaches the upstream (saves upstream cost).\n # - type: \"guard\" # size / token budget pre-check\n # config:\n # max_bytes: \"262144\" # reject bodies larger than 256 KiB (0/omit = off)\n # max_tokens: \"4096\" # reject if the request's max_tokens exceeds this\n # - type: \"injection\" # prompt-injection / jailbreak detection\n # config:\n # mode: \"block\" # \"block\" (default, hit -> 400) or \"warn\" (log only)\n # # extra_patterns — route-scoped extra rules (JSON array of {name,pattern}):\n # extra_patterns: '[{\"name\":\"secret-word\",\"pattern\":\"(?i)open sesame\"}]'\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n # ---------------------------------------------------------------------------\n # OpenAI-compatible providers (DeepSeek / Qwen / Moonshot-Kimi, ...)\n # These upstreams speak the OpenAI /chat/completions shape verbatim, so NO\n # protocol translation is needed — just point base_url at the vendor endpoint\n # and match the model prefix. PII masking applies exactly like the routes\n # above. Uncomment the ones you use and set the corresponding *_API_KEY env.\n # See the compatibility table in README for endpoints/model prefixes.\n # ---------------------------------------------------------------------------\n # - id: \"deepseek\"\n # matcher:\n # model: \"^deepseek-.*\" # deepseek-chat, deepseek-reasoner, ...\n # upstream:\n # base_url: \"https://api.deepseek.com/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DEEPSEEK_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"qwen\"\n # matcher:\n # model: \"^qwen.*\" # qwen-max, qwen-plus, qwen3-*, ...\n # upstream:\n # # Alibaba DashScope OpenAI-compatible endpoint (intl); CN users may use\n # # https://dashscope.aliyuncs.com/compatible-mode/v1\n # base_url: \"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DASHSCOPE_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"moonshot-kimi\"\n # matcher:\n # model: \"^(moonshot|kimi).*\" # kimi-k2, moonshot-v1-*, ...\n # upstream:\n # # Global endpoint; CN users may use https://api.moonshot.cn/v1\n # base_url: \"https://api.moonshot.ai/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"MOONSHOT_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # ---------------------------------------------------------------------------\n # Azure OpenAI (legacy /openai/deployments 端点)。与原生 OpenAI 两点不同:\n # 1) api-version 走 query,直接拼进 path(url = base_url + path,透传);\n # 2) 鉴权用 api-key 请求头(非 Bearer),故 auth_strategy: header + header_name。\n # base_url 用你的资源域名,path 里的 deployment 名替换成你的部署(如 gpt-4o)。\n # KEY 绝不写进本文件——只经 env:AIGIS_AZURE_KEY 注入。\n # - id: \"azure-openai\"\n # matcher:\n # model: \"^azure-.*\" # 客户端用 azure-gpt-4o 之类的 model 名\n # upstream:\n # base_url: \"https://<resource>.openai.azure.com\" # 或 env:AIGIS_AZURE_BASE_URL\n # path: \"/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview\"\n # auth_strategy: \"header\"\n # header_name: \"api-key\"\n # token_env: \"AIGIS_AZURE_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # Anthropic 原生 /v1/messages:与 OpenAI 协议不同——鉴权走 x-api-key 头(非\n # Bearer)、需带 anthropic-version 头。用 auth_strategy: none + header_policy\n # 精确设头即可,无需核心改动。base_url 指官方 https://api.anthropic.com/v1\n # 或你的中转地址;path 为 /messages。pii_claude 按 messages 结构脱敏。\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # AWS Bedrock 示例(SigV4 签名,无需 Bearer token)。\n # - 凭证走 AWS access key + secret key(环境变量)\n # - region 必填,path 形如 /model/<model-id>/invoke 或 /converse\n # - 网关用 auth_strategy: bedrock 时自动派发 Authorization / X-Amz-Date /\n # X-Amz-Content-SHA256 头,无需在 header_policy 里手填。\n # - 注意:Bedrock 走自家协议(不是 OpenAI /chat/completions),客户端要\n # 适配 body 形态;通常配合 field_map / template 转换。\n # - id: \"bedrock-claude\"\n # matcher:\n # model: \"^anthropic.claude\"\n # upstream:\n # base_url: \"https://bedrock-runtime.us-east-1.amazonaws.com\"\n # path: \"/model/anthropic.claude-3-sonnet-20240229-v1:0/invoke\"\n # auth_strategy: \"bedrock\"\n # region: \"us-east-1\"\n # access_key_env: \"AIGIS_BEDROCK_ACCESS_KEY_ID\"\n # secret_key_env: \"AIGIS_BEDROCK_SECRET_ACCESS_KEY\"\n # transforms:\n # - type: \"pii\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "6130ae732f7be6f917d2c8f963d0b173a9c5565f": {"t": 1785748962.6022172, "n": 13885, "h": "89c656f58e312a18", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n # 审计文件滚动:默认关闭(单文件,交给 logrotate)。与 log.rotate 同一套约定,\n # 切勿同时用 logrotate + 内置滚动管同一文件。/admin/audit 只查当前文件,\n # 滚动出去的历史文件是保留策略,不进查询面。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n # max_size_mb: 100 # rotate when the file exceeds this size\n # max_backups: 7 # keep at most this many rotated files\n # max_age_days: 30 # delete rotated files older than this\n # compress: true # gzip rotated files\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Response cache (non-streaming only). Identical requests within ttl_sec return\n# the previous response (X-Cache: HIT) instead of hitting the upstream. 0 =\n# disabled (default). Best for deterministic / low-sensitivity workloads — the\n# cache holds plaintext responses in memory, so keep ttl short.\ncache:\n ttl_sec: 0\n max_entries: 1000\n\n# Upstream retry (all routes). On a TRANSIENT failure — a network error, HTTP 429,\n# or any 5xx — the gateway retries with a linear backoff (backoff_ms * attempt). A\n# 4xx client error is returned immediately (a retry won't fix it). Disabled by\n# default: max_attempts <= 1 means a single try (no retry).\nretry:\n max_attempts: 1 # total tries incl. the first; set >1 to enable retry\n backoff_ms: 200 # base backoff; grows linearly per attempt\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n # token_envs — optional multi-key load balancing: list several env var\n # names and the gateway round-robins across the keys that resolve to a\n # non-empty value (env vars that are empty/unset are skipped). When set,\n # token_envs takes precedence over token_env above. Keys live only in the\n # environment, never in this file.\n # token_envs: [\"AIGIS_OPENAI_API_KEY_1\", \"AIGIS_OPENAI_API_KEY_2\"]\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n # config.rules — comma-separated rule names to apply on THIS route\n # (e.g. \"Email,Mobile Phone\"); omit/empty = all rules. Built-in names:\n # Private Key / AWS Access Key / OpenAI API Key / GitHub Token /\n # Google API Key / Email / Mobile Phone (+ any custom_rules names).\n config: {} # Uses default patterns (all rules, email: full)\n # Inbound guards (optional). Both are request-side transforms; a hit\n # aborts the request BEFORE it reaches the upstream (saves upstream cost).\n # - type: \"guard\" # size / token budget pre-check\n # config:\n # max_bytes: \"262144\" # reject bodies larger than 256 KiB (0/omit = off)\n # max_tokens: \"4096\" # reject if the request's max_tokens exceeds this\n # - type: \"injection\" # prompt-injection / jailbreak detection\n # config:\n # mode: \"block\" # \"block\" (default, hit -> 400) or \"warn\" (log only)\n # # extra_patterns — route-scoped extra rules (JSON array of {name,pattern}):\n # extra_patterns: '[{\"name\":\"secret-word\",\"pattern\":\"(?i)open sesame\"}]'\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n # ---------------------------------------------------------------------------\n # OpenAI-compatible providers (DeepSeek / Qwen / Moonshot-Kimi, ...)\n # These upstreams speak the OpenAI /chat/completions shape verbatim, so NO\n # protocol translation is needed — just point base_url at the vendor endpoint\n # and match the model prefix. PII masking applies exactly like the routes\n # above. Uncomment the ones you use and set the corresponding *_API_KEY env.\n # See the compatibility table in README for endpoints/model prefixes.\n # ---------------------------------------------------------------------------\n # - id: \"deepseek\"\n # matcher:\n # model: \"^deepseek-.*\" # deepseek-chat, deepseek-reasoner, ...\n # upstream:\n # base_url: \"https://api.deepseek.com/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DEEPSEEK_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"qwen\"\n # matcher:\n # model: \"^qwen.*\" # qwen-max, qwen-plus, qwen3-*, ...\n # upstream:\n # # Alibaba DashScope OpenAI-compatible endpoint (intl); CN users may use\n # # https://dashscope.aliyuncs.com/compatible-mode/v1\n # base_url: \"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DASHSCOPE_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"moonshot-kimi\"\n # matcher:\n # model: \"^(moonshot|kimi).*\" # kimi-k2, moonshot-v1-*, ...\n # upstream:\n # # Global endpoint; CN users may use https://api.moonshot.cn/v1\n # base_url: \"https://api.moonshot.ai/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"MOONSHOT_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # ---------------------------------------------------------------------------\n # Azure OpenAI (legacy /openai/deployments 端点)。与原生 OpenAI 两点不同:\n # 1) api-version 走 query,直接拼进 path(url = base_url + path,透传);\n # 2) 鉴权用 api-key 请求头(非 Bearer),故 auth_strategy: header + header_name。\n # base_url 用你的资源域名,path 里的 deployment 名替换成你的部署(如 gpt-4o)。\n # KEY 绝不写进本文件——只经 env:AIGIS_AZURE_KEY 注入。\n # - id: \"azure-openai\"\n # matcher:\n # model: \"^azure-.*\" # 客户端用 azure-gpt-4o 之类的 model 名\n # upstream:\n # base_url: \"https://<resource>.openai.azure.com\" # 或 env:AIGIS_AZURE_BASE_URL\n # path: \"/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview\"\n # auth_strategy: \"header\"\n # header_name: \"api-key\"\n # token_env: \"AIGIS_AZURE_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # Anthropic 原生 /v1/messages:与 OpenAI 协议不同——鉴权走 x-api-key 头(非\n # Bearer)、需带 anthropic-version 头。用 auth_strategy: none + header_policy\n # 精确设头即可,无需核心改动。base_url 指官方 https://api.anthropic.com/v1\n # 或你的中转地址;path 为 /messages。pii_claude 按 messages 结构脱敏。\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # AWS Bedrock 示例(SigV4 签名,无需 Bearer token)。\n # - 凭证走 AWS access key + secret key(环境变量)\n # - region 必填,path 形如 /model/<model-id>/invoke 或 /converse\n # - 网关用 auth_strategy: bedrock 时自动派发 Authorization / X-Amz-Date /\n # X-Amz-Content-SHA256 头,无需在 header_policy 里手填。\n # - 注意:Bedrock 走自家协议(不是 OpenAI /chat/completions),客户端要\n # 适配 body 形态;通常配合 field_map / template 转换。\n # - id: \"bedrock-claude\"\n # matcher:\n # model: \"^anthropic.claude\"\n # upstream:\n # base_url: \"https://bedrock-runtime.us-east-1.amazonaws.com\"\n # path: \"/model/anthropic.claude-3-sonnet-20240229-v1:0/invoke\"\n # auth_strategy: \"bedrock\"\n # region: \"us-east-1\"\n # access_key_env: \"AIGIS_BEDROCK_ACCESS_KEY_ID\"\n # secret_key_env: \"AIGIS_BEDROCK_SECRET_ACCESS_KEY\"\n # transforms:\n # - type: \"pii\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "ece463d0a7fbbc44ea2cfb3848ce385a970d7b2d": {"t": 1785748963.843535, "n": 12617, "h": "f18c20fdfb809ae4", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Response cache (non-streaming only). Identical requests within ttl_sec return\n# the previous response (X-Cache: HIT) instead of hitting the upstream. 0 =\n# disabled (default). Best for deterministic / low-sensitivity workloads — the\n# cache holds plaintext responses in memory, so keep ttl short.\ncache:\n ttl_sec: 0\n max_entries: 1000\n\n# Upstream retry (all routes). On a TRANSIENT failure — a network error, HTTP 429,\n# or any 5xx — the gateway retries with a linear backoff (backoff_ms * attempt). A\n# 4xx client error is returned immediately (a retry won't fix it). Disabled by\n# default: max_attempts <= 1 means a single try (no retry).\nretry:\n max_attempts: 1 # total tries incl. the first; set >1 to enable retry\n backoff_ms: 200 # base backoff; grows linearly per attempt\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n # token_envs — optional multi-key load balancing: list several env var\n # names and the gateway round-robins across the keys that resolve to a\n # non-empty value (env vars that are empty/unset are skipped). When set,\n # token_envs takes precedence over token_env above. Keys live only in the\n # environment, never in this file.\n # token_envs: [\"AIGIS_OPENAI_API_KEY_1\", \"AIGIS_OPENAI_API_KEY_2\"]\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n # config.rules — comma-separated rule names to apply on THIS route\n # (e.g. \"Email,Mobile Phone\"); omit/empty = all rules. Built-in names:\n # Private Key / AWS Access Key / OpenAI API Key / GitHub Token /\n # Google API Key / Email / Mobile Phone (+ any custom_rules names).\n config: {} # Uses default patterns (all rules, email: full)\n # Inbound guards (optional). Both are request-side transforms; a hit\n # aborts the request BEFORE it reaches the upstream (saves upstream cost).\n # - type: \"guard\" # size / token budget pre-check\n # config:\n # max_bytes: \"262144\" # reject bodies larger than 256 KiB (0/omit = off)\n # max_tokens: \"4096\" # reject if the request's max_tokens exceeds this\n # - type: \"injection\" # prompt-injection / jailbreak detection\n # config:\n # mode: \"block\" # \"block\" (default, hit -> 400) or \"warn\" (log only)\n # # extra_patterns — route-scoped extra rules (JSON array of {name,pattern}):\n # extra_patterns: '[{\"name\":\"secret-word\",\"pattern\":\"(?i)open sesame\"}]'\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n # ---------------------------------------------------------------------------\n # OpenAI-compatible providers (DeepSeek / Qwen / Moonshot-Kimi, ...)\n # These upstreams speak the OpenAI /chat/completions shape verbatim, so NO\n # protocol translation is needed — just point base_url at the vendor endpoint\n # and match the model prefix. PII masking applies exactly like the routes\n # above. Uncomment the ones you use and set the corresponding *_API_KEY env.\n # See the compatibility table in README for endpoints/model prefixes.\n # ---------------------------------------------------------------------------\n # - id: \"deepseek\"\n # matcher:\n # model: \"^deepseek-.*\" # deepseek-chat, deepseek-reasoner, ...\n # upstream:\n # base_url: \"https://api.deepseek.com/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DEEPSEEK_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"qwen\"\n # matcher:\n # model: \"^qwen.*\" # qwen-max, qwen-plus, qwen3-*, ...\n # upstream:\n # # Alibaba DashScope OpenAI-compatible endpoint (intl); CN users may use\n # # https://dashscope.aliyuncs.com/compatible-mode/v1\n # base_url: \"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DASHSCOPE_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"moonshot-kimi\"\n # matcher:\n # model: \"^(moonshot|kimi).*\" # kimi-k2, moonshot-v1-*, ...\n # upstream:\n # # Global endpoint; CN users may use https://api.moonshot.cn/v1\n # base_url: \"https://api.moonshot.ai/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"MOONSHOT_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # ---------------------------------------------------------------------------\n # Azure OpenAI (legacy /openai/deployments 端点)。与原生 OpenAI 两点不同:\n # 1) api-version 走 query,直接拼进 path(url = base_url + path,透传);\n # 2) 鉴权用 api-key 请求头(非 Bearer),故 auth_strategy: header + header_name。\n # base_url 用你的资源域名,path 里的 deployment 名替换成你的部署(如 gpt-4o)。\n # KEY 绝不写进本文件——只经 env:AIGIS_AZURE_KEY 注入。\n # - id: \"azure-openai\"\n # matcher:\n # model: \"^azure-.*\" # 客户端用 azure-gpt-4o 之类的 model 名\n # upstream:\n # base_url: \"https://<resource>.openai.azure.com\" # 或 env:AIGIS_AZURE_BASE_URL\n # path: \"/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview\"\n # auth_strategy: \"header\"\n # header_name: \"api-key\"\n # token_env: \"AIGIS_AZURE_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # Anthropic 原生 /v1/messages:与 OpenAI 协议不同——鉴权走 x-api-key 头(非\n # Bearer)、需带 anthropic-version 头。用 auth_strategy: none + header_policy\n # 精确设头即可,无需核心改动。base_url 指官方 https://api.anthropic.com/v1\n # 或你的中转地址;path 为 /messages。pii_claude 按 messages 结构脱敏。\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "4a0ba835bfe9c1bdfc461396ac153a979c173506": {"t": 1785748964.9565277, "n": 11728, "h": "00fb26d5b1b5abf9", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Response cache (non-streaming only). Identical requests within ttl_sec return\n# the previous response (X-Cache: HIT) instead of hitting the upstream. 0 =\n# disabled (default). Best for deterministic / low-sensitivity workloads — the\n# cache holds plaintext responses in memory, so keep ttl short.\ncache:\n ttl_sec: 0\n max_entries: 1000\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n # config.rules — comma-separated rule names to apply on THIS route\n # (e.g. \"Email,Mobile Phone\"); omit/empty = all rules. Built-in names:\n # Private Key / AWS Access Key / OpenAI API Key / GitHub Token /\n # Google API Key / Email / Mobile Phone (+ any custom_rules names).\n config: {} # Uses default patterns (all rules, email: full)\n # Inbound guards (optional). Both are request-side transforms; a hit\n # aborts the request BEFORE it reaches the upstream (saves upstream cost).\n # - type: \"guard\" # size / token budget pre-check\n # config:\n # max_bytes: \"262144\" # reject bodies larger than 256 KiB (0/omit = off)\n # max_tokens: \"4096\" # reject if the request's max_tokens exceeds this\n # - type: \"injection\" # prompt-injection / jailbreak detection\n # config:\n # mode: \"block\" # \"block\" (default, hit -> 400) or \"warn\" (log only)\n # # extra_patterns — route-scoped extra rules (JSON array of {name,pattern}):\n # extra_patterns: '[{\"name\":\"secret-word\",\"pattern\":\"(?i)open sesame\"}]'\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n # ---------------------------------------------------------------------------\n # OpenAI-compatible providers (DeepSeek / Qwen / Moonshot-Kimi, ...)\n # These upstreams speak the OpenAI /chat/completions shape verbatim, so NO\n # protocol translation is needed — just point base_url at the vendor endpoint\n # and match the model prefix. PII masking applies exactly like the routes\n # above. Uncomment the ones you use and set the corresponding *_API_KEY env.\n # See the compatibility table in README for endpoints/model prefixes.\n # ---------------------------------------------------------------------------\n # - id: \"deepseek\"\n # matcher:\n # model: \"^deepseek-.*\" # deepseek-chat, deepseek-reasoner, ...\n # upstream:\n # base_url: \"https://api.deepseek.com/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DEEPSEEK_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"qwen\"\n # matcher:\n # model: \"^qwen.*\" # qwen-max, qwen-plus, qwen3-*, ...\n # upstream:\n # # Alibaba DashScope OpenAI-compatible endpoint (intl); CN users may use\n # # https://dashscope.aliyuncs.com/compatible-mode/v1\n # base_url: \"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DASHSCOPE_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"moonshot-kimi\"\n # matcher:\n # model: \"^(moonshot|kimi).*\" # kimi-k2, moonshot-v1-*, ...\n # upstream:\n # # Global endpoint; CN users may use https://api.moonshot.cn/v1\n # base_url: \"https://api.moonshot.ai/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"MOONSHOT_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # ---------------------------------------------------------------------------\n # Azure OpenAI (legacy /openai/deployments 端点)。与原生 OpenAI 两点不同:\n # 1) api-version 走 query,直接拼进 path(url = base_url + path,透传);\n # 2) 鉴权用 api-key 请求头(非 Bearer),故 auth_strategy: header + header_name。\n # base_url 用你的资源域名,path 里的 deployment 名替换成你的部署(如 gpt-4o)。\n # KEY 绝不写进本文件——只经 env:AIGIS_AZURE_KEY 注入。\n # - id: \"azure-openai\"\n # matcher:\n # model: \"^azure-.*\" # 客户端用 azure-gpt-4o 之类的 model 名\n # upstream:\n # base_url: \"https://<resource>.openai.azure.com\" # 或 env:AIGIS_AZURE_BASE_URL\n # path: \"/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview\"\n # auth_strategy: \"header\"\n # header_name: \"api-key\"\n # token_env: \"AIGIS_AZURE_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # Anthropic 原生 /v1/messages:与 OpenAI 协议不同——鉴权走 x-api-key 头(非\n # Bearer)、需带 anthropic-version 头。用 auth_strategy: none + header_policy\n # 精确设头即可,无需核心改动。base_url 指官方 https://api.anthropic.com/v1\n # 或你的中转地址;path 为 /messages。pii_claude 按 messages 结构脱敏。\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "71931432dd2d8b522ec4559f3feb517a74a701ab": {"t": 1785748966.0706856, "n": 10955, "h": "08e99368226e49e8", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Response cache (non-streaming only). Identical requests within ttl_sec return\n# the previous response (X-Cache: HIT) instead of hitting the upstream. 0 =\n# disabled (default). Best for deterministic / low-sensitivity workloads — the\n# cache holds plaintext responses in memory, so keep ttl short.\ncache:\n ttl_sec: 0\n max_entries: 1000\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n # config.rules — comma-separated rule names to apply on THIS route\n # (e.g. \"Email,Mobile Phone\"); omit/empty = all rules. Built-in names:\n # Private Key / AWS Access Key / OpenAI API Key / GitHub Token /\n # Google API Key / Email / Mobile Phone (+ any custom_rules names).\n config: {} # Uses default patterns (all rules, email: full)\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n # ---------------------------------------------------------------------------\n # OpenAI-compatible providers (DeepSeek / Qwen / Moonshot-Kimi, ...)\n # These upstreams speak the OpenAI /chat/completions shape verbatim, so NO\n # protocol translation is needed — just point base_url at the vendor endpoint\n # and match the model prefix. PII masking applies exactly like the routes\n # above. Uncomment the ones you use and set the corresponding *_API_KEY env.\n # See the compatibility table in README for endpoints/model prefixes.\n # ---------------------------------------------------------------------------\n # - id: \"deepseek\"\n # matcher:\n # model: \"^deepseek-.*\" # deepseek-chat, deepseek-reasoner, ...\n # upstream:\n # base_url: \"https://api.deepseek.com/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DEEPSEEK_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"qwen\"\n # matcher:\n # model: \"^qwen.*\" # qwen-max, qwen-plus, qwen3-*, ...\n # upstream:\n # # Alibaba DashScope OpenAI-compatible endpoint (intl); CN users may use\n # # https://dashscope.aliyuncs.com/compatible-mode/v1\n # base_url: \"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DASHSCOPE_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"moonshot-kimi\"\n # matcher:\n # model: \"^(moonshot|kimi).*\" # kimi-k2, moonshot-v1-*, ...\n # upstream:\n # # Global endpoint; CN users may use https://api.moonshot.cn/v1\n # base_url: \"https://api.moonshot.ai/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"MOONSHOT_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # ---------------------------------------------------------------------------\n # Azure OpenAI (legacy /openai/deployments 端点)。与原生 OpenAI 两点不同:\n # 1) api-version 走 query,直接拼进 path(url = base_url + path,透传);\n # 2) 鉴权用 api-key 请求头(非 Bearer),故 auth_strategy: header + header_name。\n # base_url 用你的资源域名,path 里的 deployment 名替换成你的部署(如 gpt-4o)。\n # KEY 绝不写进本文件——只经 env:AIGIS_AZURE_KEY 注入。\n # - id: \"azure-openai\"\n # matcher:\n # model: \"^azure-.*\" # 客户端用 azure-gpt-4o 之类的 model 名\n # upstream:\n # base_url: \"https://<resource>.openai.azure.com\" # 或 env:AIGIS_AZURE_BASE_URL\n # path: \"/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview\"\n # auth_strategy: \"header\"\n # header_name: \"api-key\"\n # token_env: \"AIGIS_AZURE_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # Anthropic 原生 /v1/messages:与 OpenAI 协议不同——鉴权走 x-api-key 头(非\n # Bearer)、需带 anthropic-version 头。用 auth_strategy: none + header_policy\n # 精确设头即可,无需核心改动。base_url 指官方 https://api.anthropic.com/v1\n # 或你的中转地址;path 为 /messages。pii_claude 按 messages 结构脱敏。\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "a099cd1b1861e0a20ce97176c7e94c6051cac4d2": {"t": 1785748967.1527216, "n": 9803, "h": "755136b5376d4f28", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Response cache (non-streaming only). Identical requests within ttl_sec return\n# the previous response (X-Cache: HIT) instead of hitting the upstream. 0 =\n# disabled (default). Best for deterministic / low-sensitivity workloads — the\n# cache holds plaintext responses in memory, so keep ttl short.\ncache:\n ttl_sec: 0\n max_entries: 1000\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n # config.rules — comma-separated rule names to apply on THIS route\n # (e.g. \"Email,Mobile Phone\"); omit/empty = all rules. Built-in names:\n # Private Key / AWS Access Key / OpenAI API Key / GitHub Token /\n # Google API Key / Email / Mobile Phone (+ any custom_rules names).\n config: {} # Uses default patterns (all rules, email: full)\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n # ---------------------------------------------------------------------------\n # OpenAI-compatible providers (DeepSeek / Qwen / Moonshot-Kimi, ...)\n # These upstreams speak the OpenAI /chat/completions shape verbatim, so NO\n # protocol translation is needed — just point base_url at the vendor endpoint\n # and match the model prefix. PII masking applies exactly like the routes\n # above. Uncomment the ones you use and set the corresponding *_API_KEY env.\n # See the compatibility table in README for endpoints/model prefixes.\n # ---------------------------------------------------------------------------\n # - id: \"deepseek\"\n # matcher:\n # model: \"^deepseek-.*\" # deepseek-chat, deepseek-reasoner, ...\n # upstream:\n # base_url: \"https://api.deepseek.com/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DEEPSEEK_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"qwen\"\n # matcher:\n # model: \"^qwen.*\" # qwen-max, qwen-plus, qwen3-*, ...\n # upstream:\n # # Alibaba DashScope OpenAI-compatible endpoint (intl); CN users may use\n # # https://dashscope.aliyuncs.com/compatible-mode/v1\n # base_url: \"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"DASHSCOPE_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - id: \"moonshot-kimi\"\n # matcher:\n # model: \"^(moonshot|kimi).*\" # kimi-k2, moonshot-v1-*, ...\n # upstream:\n # # Global endpoint; CN users may use https://api.moonshot.cn/v1\n # base_url: \"https://api.moonshot.ai/v1\"\n # path: \"/chat/completions\"\n # auth_strategy: \"bearer\"\n # token_env: \"MOONSHOT_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "d18c09c9d6aa12120e9e661f4df5b5a524d8e41c": {"t": 1785748968.264472, "n": 7843, "h": "934308cc8096e80b", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Response cache (non-streaming only). Identical requests within ttl_sec return\n# the previous response (X-Cache: HIT) instead of hitting the upstream. 0 =\n# disabled (default). Best for deterministic / low-sensitivity workloads — the\n# cache holds plaintext responses in memory, so keep ttl short.\ncache:\n ttl_sec: 0\n max_entries: 1000\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n # config.rules — comma-separated rule names to apply on THIS route\n # (e.g. \"Email,Mobile Phone\"); omit/empty = all rules. Built-in names:\n # Private Key / AWS Access Key / OpenAI API Key / GitHub Token /\n # Google API Key / Email / Mobile Phone (+ any custom_rules names).\n config: {} # Uses default patterns (all rules, email: full)\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "fdfb9307a904fc886687c180f6194f6b972e871e": {"t": 1785748969.3672569, "n": 7503, "h": "d229dfb40d990107", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n # config.rules — comma-separated rule names to apply on THIS route\n # (e.g. \"Email,Mobile Phone\"); omit/empty = all rules. Built-in names:\n # Private Key / AWS Access Key / OpenAI API Key / GitHub Token /\n # Google API Key / Email / Mobile Phone (+ any custom_rules names).\n config: {} # Uses default patterns (all rules, email: full)\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "5b6d9060a34c4e02a1eedf2af94a846e6e246e76": {"t": 1785748970.6397302, "n": 7175, "h": "f3797af54b68d9ce", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Per-route circuit breaker. When an upstream fails fail_threshold times in a\n# row, that route's circuit opens and returns HTTP 503 for cooldown_sec; then it\n# lets one probe through (half-open) and closes on success. Disabled by default.\nbreaker:\n enabled: false\n fail_threshold: 5\n cooldown_sec: 30\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n config: {} # Uses default patterns (email: full)\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "43cd9c28a227de29f854891b1489ba768cba5a99": {"t": 1785748971.7691379, "n": 6869, "h": "d1068d367aee375c", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Admission control. max_concurrent caps the number of requests processed at\n# once; excess requests get HTTP 429 (Too Many Requests). 0 = unlimited (default).\nlimit:\n max_concurrent: 0\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n config: {} # Uses default patterns (email: full)\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "a8ceb9e3e34382cd8b1823953c1d5ab399d1df71": {"t": 1785748973.0093067, "n": 6681, "h": "5ad48d79a47cc972", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n config: {} # Uses default patterns (email: full)\n # Gemini route: Gemini models via an OpenAI-compatible aggregator endpoint\n # (e.g. aihubmix / inferera), so request/response stay in OpenAI shape — no\n # protocol translation needed (unlike Dify). Uses AIGIS_GEMINI_BASE_URL (/v1)\n # + GEMINI_API_KEY.\n - id: \"gemini-default\"\n matcher:\n model: \"^gemini-.*\" # gemini-2.5-flash, gemini-2.0-flash, ...\n upstream:\n base_url: \"env:AIGIS_GEMINI_BASE_URL\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"GEMINI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "b8ce7a6d655f6bb65369a2f0a5726f334b222010": {"t": 1785748974.2231169, "n": 6078, "h": "3c92a3431e1a00f1", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Custom sensitive-info detection rules (optional). Each rule's pattern is a Go\n# regexp; matches are tokenized (masked->vault->unmasked) exactly like the\n# built-in rules (email/phone/keys). Invalid regex fails loud at startup.\n# security:\n# custom_rules:\n# - name: \"ID Card\" # 中国身份证(18 位)\n# pattern: '\\b\\d{17}[\\dXx]\\b'\n# - name: \"Bank Card\"\n# pattern: '\\b\\d{16,19}\\b'\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n config: {} # Uses default patterns (email: full)\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "b01224e9148381369af73ef3055fffc04c7de117": {"t": 1785748975.3067453, "n": 5676, "h": "d1ea1f998e70fbc8", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n # config.email tunes email tokenization:\n # (omitted)/\"full\" — tokenize the whole address (default, most strict)\n # \"local\" — tokenize only the local part, keep \"@domain\" so\n # the model still sees the domain. Unmask restores\n # the full address on the way back regardless.\n config: {} # Uses default patterns (email: full)\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config:\n email: \"local\" # keep @domain visible to dify, tokenize only the mailbox\n - type: \"template\"\n config:\n # response_mode follows the client's \"stream\" flag: streaming when the\n # client wants SSE (translated back by stream_translate below), else\n # blocking (translated by response_transforms below).\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": {{if .stream}}\"streaming\"{{else}}\"blocking\"{{end}},\n \"user\": {{json .user}}\n }\n # 流式响应翻译:dify SSE 事件流 -> OpenAI chat.completion.chunk 流\n stream_translate: \"dify\"\n # 非流式响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "3dd3620925538996fc1231017be1d184b3af7ce2": {"t": 1785748976.4004061, "n": 4838, "h": "cbe5ad80c4435126", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n config: {} # Uses default patterns\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n - type: \"template\"\n config:\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": \"blocking\",\n \"user\": {{json .user}}\n }\n # 响应转换:把 dify 原生响应翻译回 OpenAI chat-completion 形态\n response_transforms:\n - type: \"template\"\n config:\n template: |\n {\n \"id\": {{json .message_id}},\n \"object\": \"chat.completion\",\n \"created\": {{json .created_at}},\n \"model\": \"dify\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\"role\": \"assistant\", \"content\": {{json .answer}}},\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {{json .metadata.usage}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "fd228326c3caf49ca77df253103a080f1c5c04c1": {"t": 1785748977.486142, "n": 4188, "h": "b1e375a5c8585e8b", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n config: {} # Uses default patterns\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify chat route: translates an OpenAI-style chat request into Dify's\n # /chat-messages payload (chat/agent/chatflow app). PII is masked first, then\n # the body is reshaped by the template. Uses AIGIS_DIFY_BASE_URL / AIGIS_DIFY_API_KEY.\n # NOTE: for a Workflow-type app use path \"/workflows/run\" and put the query\n # under \"inputs\"; for a Completion app use \"/completion-messages\".\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"env:AIGIS_DIFY_BASE_URL\"\n path: \"/chat-messages\"\n auth_strategy: \"bearer\"\n token_env: \"AIGIS_DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n - type: \"template\"\n config:\n template: |\n {\n \"inputs\": {},\n \"query\": {{json (index .messages 0 \"content\")}},\n \"response_mode\": \"blocking\",\n \"user\": {{json .user}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "923a031dbf5e3a7be3c467ec6d62f8512b0cf6a0": {"t": 1785748978.5975456, "n": 4021, "h": "88315c555939a332", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n file: \"./logs/aigis.log\"\n # 日志滚动:默认关闭(单文件),交给系统 logrotate 处理。\n # 若不用 logrotate,设 rotate: true 启用内置 lumberjack 滚动。\n # 切勿同时用 logrotate + lumberjack 管同一文件(会互相冲突)。\n rotate: false\n # 下列参数仅在 rotate: true 时生效:\n max_size_mb: 100 # rotate when the file exceeds this size\n max_backups: 7 # keep at most this many rotated files\n max_age_days: 30 # delete rotated files older than this\n compress: true # gzip rotated files\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n config: {} # Uses default patterns\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Dify workflow route: translates an OpenAI-style chat request into Dify's\n # /workflows/run payload. PII is masked first, then the body is reshaped by\n # the template. Requires DIFY_API_KEY in the environment.\n # NOTE: the `json` template func quotes+escapes the value, so do NOT wrap the\n # {{json ...}} call in literal quotes — it already emits a JSON string.\n - id: \"dify-workflow\"\n matcher:\n model: \"^dify-.*\"\n upstream:\n base_url: \"https://api.dify.ai/v1\"\n path: \"/workflows/run\"\n auth_strategy: \"bearer\"\n token_env: \"DIFY_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n - type: \"template\"\n config:\n template: |\n {\n \"inputs\": {\n \"query\": {{json (index .messages 0 \"content\")}}\n },\n \"response_mode\": \"blocking\",\n \"user\": {{json .user}}\n }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "f0db61d5ca0f83c6f124c26a45366b9c5d2dc87a": {"t": 1785748979.6829107, "n": 3284, "h": "c8697a981fc8d4af", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n\n# Sensitive-info audit trail.\n# When enabled, every request that masks sensitive info appends ONE metadata-only\n# JSON line to ./logs/audit.jsonl (rule type + counts + placeholders, NO plaintext).\n# Clean requests produce no line. Defaults to true if this section is omitted.\naudit:\n enabled: true\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n config: {} # Uses default patterns\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Example: Dify route (commented out)\n # - id: \"dify-workflow\"\n # matcher:\n # model: \"^dify-.*\"\n # upstream:\n # base_url: \"https://api.dify.ai/v1\"\n # path: \"/workflows/run\"\n # auth_strategy: \"bearer\"\n # token_env: \"DIFY_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - type: \"template\"\n # config:\n # template: |\n # {\n # \"inputs\": {\n # \"query\": \"{{index .messages 0 \"content\"}}\"\n # },\n # \"response_mode\": \"blocking\",\n # \"user\": \"{{.user}}\"\n # }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "e6fd7fb2fbf9c288dac48f110cdcd517cb2938ac": {"t": 1785748980.7618446, "n": 2984, "h": "a310abba6a0db3b6", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n config: {} # Uses default patterns\n - id: \"claude-proxy\"\n matcher:\n model: \"^(claude|glm).*\"\n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Example: Dify route (commented out)\n # - id: \"dify-workflow\"\n # matcher:\n # model: \"^dify-.*\"\n # upstream:\n # base_url: \"https://api.dify.ai/v1\"\n # path: \"/workflows/run\"\n # auth_strategy: \"bearer\"\n # token_env: \"DIFY_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - type: \"template\"\n # config:\n # template: |\n # {\n # \"inputs\": {\n # \"query\": \"{{index .messages 0 \"content\"}}\"\n # },\n # \"response_mode\": \"blocking\",\n # \"user\": \"{{.user}}\"\n # }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "dabef7eb1eb58b153d7c36d407a1521fac63de33": {"t": 1785748981.8883967, "n": 2979, "h": "c03ea12f7023bd8f", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"debug\"\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n config: {} # Uses default patterns\n - id: \"claude-proxy\"\n matcher:\n model: \"^claude.*\" \n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Example: Dify route (commented out)\n # - id: \"dify-workflow\"\n # matcher:\n # model: \"^dify-.*\"\n # upstream:\n # base_url: \"https://api.dify.ai/v1\"\n # path: \"/workflows/run\"\n # auth_strategy: \"bearer\"\n # token_env: \"DIFY_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - type: \"template\"\n # config:\n # template: |\n # {\n # \"inputs\": {\n # \"query\": \"{{index .messages 0 \"content\"}}\"\n # },\n # \"response_mode\": \"blocking\",\n # \"user\": \"{{.user}}\"\n # }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "0b8b40bb602e29bf3fe837a3eb09f60bb7a75f7f": {"t": 1785748982.9749243, "n": 2978, "h": "6a3ce72f029b03e6", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"info\"\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n config: {} # Uses default patterns\n - id: \"claude-proxy\"\n matcher:\n model: \"^claude.*\" \n upstream:\n base_url: \"env:AIGIS_ANTHROPIC_BASE_URL\" # 或者你的中转地址\n path: \"/messages\"\n # 注意:Claude 不使用 Bearer Token,而是 header 里的 x-api-key\n # 所以这里 auth_strategy 设为 none,我们用 header_policy 来处理\n auth_strategy: \"none\" \n \n header_policy:\n allow: [\"anthropic-version\", \"content-type\", \"anthropic-beta\"]\n set:\n \"x-api-key\": \"env:AIGIS_ANTHROPIC_KEY\" # 你的真实 Key\n remove: [\"authorization\"] # 移除可能误传的 Bearer\n\n transforms:\n - type: \"pii_claude\"\n config: {}\n # Example: Dify route (commented out)\n # - id: \"dify-workflow\"\n # matcher:\n # model: \"^dify-.*\"\n # upstream:\n # base_url: \"https://api.dify.ai/v1\"\n # path: \"/workflows/run\"\n # auth_strategy: \"bearer\"\n # token_env: \"DIFY_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - type: \"template\"\n # config:\n # template: |\n # {\n # \"inputs\": {\n # \"query\": \"{{index .messages 0 \"content\"}}\"\n # },\n # \"response_mode\": \"blocking\",\n # \"user\": \"{{.user}}\"\n # }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "79331bc7843032e3271cdece5d965d45ffea29ff": {"t": 1785748984.0431397, "n": 2367, "h": "106e6e955e94a491", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"info\"\n\n# Legacy OpenAI config (used as fallback if no engine.routes configured)\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n\n# Transformation Engine Configuration\n# If routes are configured here, they take precedence over legacy openai config\nengine:\n routes:\n # Default OpenAI route - matches all requests with gpt models\n - id: \"openai-default\"\n matcher:\n model: \"^gpt-.*\" # Regex: matches gpt-3.5-turbo, gpt-4, etc.\n upstream:\n base_url: \"https://aihubmix.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\" # bearer, header, query\n token_env: \"AIGIS_OPENAI_API_KEY\" # Environment variable name\n transforms:\n - type: \"pii\"\n config: {} # Uses default patterns\n\n # Example: Dify route (commented out)\n # - id: \"dify-workflow\"\n # matcher:\n # model: \"^dify-.*\"\n # upstream:\n # base_url: \"https://api.dify.ai/v1\"\n # path: \"/workflows/run\"\n # auth_strategy: \"bearer\"\n # token_env: \"DIFY_API_KEY\"\n # transforms:\n # - type: \"pii\"\n # config: {}\n # - type: \"template\"\n # config:\n # template: |\n # {\n # \"inputs\": {\n # \"query\": \"{{index .messages 0 \"content\"}}\"\n # },\n # \"response_mode\": \"blocking\",\n # \"user\": \"{{.user}}\"\n # }\n\n # Example: Field mapping transform (commented out)\n # - id: \"custom-api\"\n # matcher:\n # model: \"^custom-.*\"\n # upstream:\n # base_url: \"https://custom-api.example.com\"\n # path: \"/generate\"\n # auth_strategy: \"header\"\n # header_name: \"X-API-Key\"\n # token_env: \"CUSTOM_API_KEY\"\n # transforms:\n # - type: \"field_map\"\n # config:\n # \"prompt\": \"messages.0.content\" # target: source\n # \"max_tokens\": \"max_tokens\"\n\n # Catch-all route (matches everything, should be last)\n - id: \"fallback\"\n matcher: {} # Empty matcher = matches all\n upstream:\n base_url: \"https://api.openai.com/v1\"\n path: \"/chat/completions\"\n auth_strategy: \"bearer\"\n token_env: \"OPENAI_API_KEY\"\n transforms:\n - type: \"pii\"\n config: {}\n"}, "207fa58c05b7c57f37b5ca4cd6c09f084fa85754": {"t": 1785748985.1034698, "n": 149, "h": "b65d5e7cf6d3e4fe", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlog:\n level: \"info\"\n\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n"}, "ad84d2249c590ff555022a60cf99b142296bb0d9": {"t": 1785748986.147438, "n": 170, "h": "31c0c3146dd5df48", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlogging:\n level: \"info\"\n format: \"json\"\n\nopenai:\n api_key: \"\"\n base_url: \"https://api.openai.com/v1\"\n model: \"gpt-3.5-turbo\"\n"}, "67d9e1e092ad68c656ef727a5e53aeda95e1c4da": {"t": 1785748987.2430437, "n": 82, "h": "931e6b1ff8c70b22", "c": "server:\n host: \"0.0.0.0\"\n port: 8080\n\nlogging:\n level: \"info\"\n format: \"json\"\n"}}}}