Files
inquiry_robot/inquiry-agent/agent/config/__init__.py
T
jillion886andCursor 1a2e1d6cb2 补齐成交跟进:按线路推成交通知、收下未成交原因、协商中按工作日再问。
成交走询价消息服务;陆运/海运私聊下一句能记下原因;协商中回执写明顺延三个工作日和下次 9:00。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-19 16:08:48 +08:00

184 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
配置包:只读环境变量,组装 Settings。
本文件职责:集中声明 YTD_ENV、端口、企微与库变量名(对齐 prompt/13、09)。
禁止:在本包写业务 if/else;禁止把密钥默认值写进源码。
"""
from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
def _find_dotenv() -> str:
"""自 agent 目录向上找仓库根 .env;找不到则返回空,由环境变量本身提供。"""
here = Path(__file__).resolve()
for parent in here.parents:
candidate = parent / ".env"
if candidate.is_file():
return str(candidate)
return ""
class Settings(BaseSettings):
"""
进程级只读配置。
调用时机:HTTP / Worker 启动时通过 get_settings() 加载一次。
企微密钥必须来自环境/.env,源码中不得出现明文 Secret。
"""
model_config = SettingsConfigDict(
env_file=_find_dotenv() or None,
env_file_encoding="utf-8",
extra="ignore",
)
ytd_env: str = Field(default="test", alias="YTD_ENV")
agent_http_port: int = Field(default=8910, alias="AGENT_HTTP_PORT")
public_base_url: str = Field(default="", alias="PUBLIC_BASE_URL")
inquiry_form_public_base_url: str = Field(
default="",
alias="INQUIRY_FORM_PUBLIC_BASE_URL",
)
biz_api_base_url: str = Field(default="http://127.0.0.1:8180/jeecgboot", alias="BIZ_API_BASE_URL")
biz_api_token: str = Field(default="", alias="BIZ_API_TOKEN")
biz_api_username: str = Field(default="", alias="BIZ_API_USERNAME")
biz_api_password: str = Field(default="", alias="BIZ_API_PASSWORD")
biz_api_timeout_seconds: float = Field(default=10.0, alias="BIZ_API_TIMEOUT_SECONDS")
# http=打 8180 真主账/真 TMS;memory=单测假账本
ledger_backend: str = Field(default="http", alias="LEDGER_BACKEND")
agent_callback_token: str = Field(default="", alias="AGENT_CALLBACK_TOKEN")
# 兼容现网变量名
inquiry_agent_callback_token: str = Field(default="", alias="INQUIRY_AGENT_CALLBACK_TOKEN")
# 企微询价应用(与正式同一套应用 ID;密钥只从环境读)
wecom_corp_id: str = Field(default="", alias="WECOM_CORP_ID")
wecom_agent_id: str = Field(default="1000010", alias="WECOM_AGENT_ID")
wecom_secret: str = Field(default="", alias="WECOM_SECRET")
# 现网「询价消息服务」:只推成交通知,不走询价小助手 1000010。
wecom_notify_agent_id: str = Field(default="", alias="WECOM_NOTIFY_AGENT_ID")
wecom_notify_secret: str = Field(default="", alias="WECOM_NOTIFY_SECRET")
wecom_archive_seat_user_ids: str = Field(
default="",
alias="WECOM_ARCHIVE_SEAT_USER_IDS",
)
# 微盛企微管家:Worker 轮询群消息。游标只写本进程 Redis 前缀,禁止去推现网官方 seq。
wshoto_base_url: str = Field(default="https://open.wshoto.com", alias="WSHOTO_BASE_URL")
wshoto_app_id: str = Field(default="", alias="WSHOTO_APP_ID")
wshoto_app_secret: str = Field(default="", alias="WSHOTO_APP_SECRET")
wshoto_tenant_id: str = Field(default="", alias="WSHOTO_TENANT_ID")
wshoto_poll_enabled: bool = Field(default=True, alias="WSHOTO_POLL_ENABLED")
# 群消息靠会话存档,扫得越勤,听到得越快。热窗内还会再压到 1 秒。
wshoto_poll_interval_sec: float = Field(default=2.0, alias="WSHOTO_POLL_INTERVAL_SEC")
wshoto_bootstrap_lookback_sec: int = Field(
default=10800,
alias="WSHOTO_BOOTSTRAP_LOOKBACK_SEC",
)
wshoto_page_size: int = Field(default=50, alias="WSHOTO_PAGE_SIZE")
# 空运群是人自建的,存档席可能没拉进群。按这些 userid 扫新群(逗号分隔)。
wshoto_scan_user_ids: str = Field(default="", alias="WSHOTO_SCAN_USER_IDS")
wshoto_attachment_min_interval_ms: int = Field(
default=1050,
alias="WSHOTO_ATTACHMENT_MIN_INTERVAL_MS",
)
wecom_callback_token: str = Field(default="", alias="WECOM_CALLBACK_TOKEN")
wecom_encoding_aes_key: str = Field(default="", alias="WECOM_ENCODING_AES_KEY")
wecom_callback_reply_text: str = Field(
default="询价智能体接通成功。已收到:{content}",
alias="WECOM_CALLBACK_REPLY_TEXT",
)
# 本机注入开关:test 默认开;prod 默认关
dev_inject_enabled: bool = Field(default=False, alias="DEV_INJECT_ENABLED")
pg_host: str = Field(default="", alias="PG_HOST")
pg_port: int = Field(default=5432, alias="PG_PORT")
pg_database: str = Field(default="", alias="PG_DATABASE")
pg_user: str = Field(default="", alias="PG_USER")
pg_password: str = Field(default="", alias="PG_PASSWORD")
pg_sslmode: str = Field(default="prefer", alias="PG_SSLMODE")
# 可直接给完整 DSN;为空则由 host/port/user 等拼装
pg_dsn: str = Field(default="", alias="PG_DSN")
# postgres | memory;正式必须 postgres;memory 仅本地骨架冒烟
checkpoint_backend: str = Field(default="postgres", alias="CHECKPOINT_BACKEND")
redis_url: str = Field(default="", alias="REDIS_URL")
redis_key_prefix: str = Field(default="inquiry_robot:", alias="REDIS_KEY_PREFIX")
# redis | memory;正式必须 redis + URL 指向逻辑库 /1(与现网 /0 隔离)
redis_backend: str = Field(default="redis", alias="REDIS_BACKEND")
redis_required: bool = Field(default=False, alias="REDIS_REQUIRED")
# auto | memory | postgres;auto=有 PG_DSN 则尝试 PG
message_store_backend: str = Field(default="auto", alias="MESSAGE_STORE_BACKEND")
wecom_aibot_bridge_token: str = Field(default="", alias="WECOM_AIBOT_BRIDGE_TOKEN")
wecom_aibot_bridge_url: str = Field(
default="http://127.0.0.1:8913",
alias="WECOM_AIBOT_BRIDGE_URL",
)
wecom_aibot_ingress_url: str = Field(
default="http://127.0.0.1:8910/internal/aibot/inbound",
alias="WECOM_AIBOT_INGRESS_URL",
)
deepseek_api_key: str = Field(default="", alias="DEEPSEEK_API_KEY")
deepseek_base_url: str = Field(default="https://api.deepseek.com", alias="DEEPSEEK_BASE_URL")
deepseek_model: str = Field(default="", alias="DEEPSEEK_MODEL")
qwen_api_key: str = Field(default="", alias="QWEN_API_KEY")
qwen_base_url: str = Field(default="", alias="QWEN_BASE_URL")
qwen_text_model: str = Field(default="", alias="QWEN_TEXT_MODEL")
qwen_vl_model: str = Field(default="", alias="QWEN_VL_MODEL")
# prompt/04:未确认不得打真实模型(出站 HTTP 客户端强制检查)
llm_data_usage_confirmed: bool = Field(default=False, alias="LLM_DATA_USAGE_CONFIRMED")
# Worker 槽是否允许对 ordered_actions 走真网(仍受上一开关约束)
llm_allow_network: bool = Field(default=False, alias="LLM_ALLOW_NETWORK")
# LibreOffice:默认 Windows 安装路径;Linux 可改 /usr/bin/libreoffice
libreoffice_path: str = Field(
default=r"C:\Program Files\LibreOffice\program\soffice.exe",
alias="LIBREOFFICE_PATH",
)
libreoffice_timeout_seconds: float = Field(
default=120.0, alias="LIBREOFFICE_TIMEOUT_SECONDS"
)
object_storage_endpoint_url: str = Field(default="", alias="OBJECT_STORAGE_ENDPOINT_URL")
object_storage_bucket: str = Field(default="inquiry-robot", alias="OBJECT_STORAGE_BUCKET")
object_storage_access_key: str = Field(default="", alias="OBJECT_STORAGE_ACCESS_KEY")
object_storage_secret_key: str = Field(default="", alias="OBJECT_STORAGE_SECRET_KEY")
object_storage_path_style: bool = Field(default=True, alias="OBJECT_STORAGE_PATH_STYLE")
libreoffice_slots: int = Field(default=1, alias="LIBREOFFICE_SLOTS")
http_job_slots: int = Field(default=3, alias="HTTP_JOB_SLOTS")
def resolve_pg_dsn(self) -> str:
"""
解析 PostgreSQL 连接串(运行账 / Graph checkpoint)。
返回值:PG_DSN 或由分量拼出的 URI;缺关键字段时返回空串。
禁止:连现网 ytd_runtime(由部署/环境保证库名,代码不写死切换)。
"""
raw = (self.pg_dsn or "").strip()
if raw:
return raw
if not (self.pg_host and self.pg_database and self.pg_user):
return ""
# 密码可为空(本机信任);sslmode 走环境
from urllib.parse import quote_plus
user = quote_plus(self.pg_user)
password = quote_plus(self.pg_password or "")
auth = f"{user}:{password}@" if self.pg_password else f"{user}@"
return (
f"postgresql://{auth}{self.pg_host}:{self.pg_port}/"
f"{self.pg_database}?sslmode={self.pg_sslmode or 'prefer'}"
)
@lru_cache(maxsize=1)
def get_settings() -> Settings:
"""返回进程内单例 Settings。副作用:首次可能读 .env。"""
return Settings()