Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chenguojun06-star_fz66666.json
T

1 line
352 KiB
JSON
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.
{"repo": "chenguojun06-star/fz66666", "files": {"cloudbaserc.json": {"92136382831779e22f23b72a08f4e137bfa17dd3": {"t": 1785745313.773528, "n": 3154, "h": "d7faedb3f54e9911", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-backend\",\n \"servicePath\": \"/api\",\n \"localPath\": \"./backend\",\n \"dockerfilePath\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"initialDelaySeconds\": 300,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"AGNES_API_KEY\": \"{{AGNES_API_KEY}}\",\n \"AGNES_MODEL\": \"{{AGNES_MODEL:agnes-2.5-flash}}\",\n \"AGNES2_MODEL\": \"{{AGNES2_MODEL:agnes-2.5-flash}}\",\n \"AI_MODEL_VISION\": \"{{AI_MODEL_VISION:agnes-2.5-flash}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\",\n \"AI_IM_AI_FEISHU_ENABLED\": \"true\",\n \"AI_IM_AI_FEISHU_VERIFICATION_TOKEN\": \"dxZCfyFE816hLOrPbpE3pzdHJpmcXAMQ\",\n \"AI_IM_AI_FEISHU_ENCRYPT_KEY\": \"7qZik8wobG3FJVt8rrg97esIHfRRV6dc\"\n }\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-admin\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"dockerfilePath\": \"./frontend/Dockerfile\",\n \"containerPort\": 80,\n \"initialDelaySeconds\": 30,\n \"minNum\": 1,\n \"maxNum\": 3,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"envVariables\": {\n \"BACKEND_URL\": \"https://api.webyszl.cn\"\n }\n }\n },\n \"h5\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-h5\",\n \"servicePath\": \"/h5\",\n \"localPath\": \"./h5-web\",\n \"dockerfilePath\": \"./h5-web/Dockerfile\",\n \"containerPort\": 80,\n \"initialDelaySeconds\": 30,\n \"minNum\": 1,\n \"maxNum\": 3,\n \"cpu\": 0.25,\n \"mem\": 0.5\n }\n }\n }\n }\n}"}, "e3eca03c3b9734ba801c15ce6bafd09fa6b8584a": {"t": 1785749953.5917158, "n": 3154, "h": "d7faedb3f54e9911", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-backend\",\n \"servicePath\": \"/api\",\n \"localPath\": \"./backend\",\n \"dockerfilePath\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"initialDelaySeconds\": 300,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"AGNES_API_KEY\": \"{{AGNES_API_KEY}}\",\n \"AGNES_MODEL\": \"{{AGNES_MODEL:agnes-2.5-flash}}\",\n \"AGNES2_MODEL\": \"{{AGNES2_MODEL:agnes-2.5-flash}}\",\n \"AI_MODEL_VISION\": \"{{AI_MODEL_VISION:agnes-2.5-flash}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\",\n \"AI_IM_AI_FEISHU_ENABLED\": \"true\",\n \"AI_IM_AI_FEISHU_VERIFICATION_TOKEN\": \"dxZCfyFE816hLOrPbpE3pzdHJpmcXAMQ\",\n \"AI_IM_AI_FEISHU_ENCRYPT_KEY\": \"7qZik8wobG3FJVt8rrg97esIHfRRV6dc\"\n }\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-admin\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"dockerfilePath\": \"./frontend/Dockerfile\",\n \"containerPort\": 80,\n \"initialDelaySeconds\": 30,\n \"minNum\": 1,\n \"maxNum\": 3,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"envVariables\": {\n \"BACKEND_URL\": \"https://api.webyszl.cn\"\n }\n }\n },\n \"h5\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-h5\",\n \"servicePath\": \"/h5\",\n \"localPath\": \"./h5-web\",\n \"dockerfilePath\": \"./h5-web/Dockerfile\",\n \"containerPort\": 80,\n \"initialDelaySeconds\": 30,\n \"minNum\": 1,\n \"maxNum\": 3,\n \"cpu\": 0.25,\n \"mem\": 0.5\n }\n }\n }\n }\n}"}, "260347e68db7ecd42554f892d362755a81e6df07": {"t": 1785749954.6667893, "n": 2958, "h": "7d5d9fff4061f6be", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-backend\",\n \"servicePath\": \"/api\",\n \"localPath\": \"./backend\",\n \"dockerfilePath\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"initialDelaySeconds\": 300,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"AGNES_API_KEY\": \"{{AGNES_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\",\n \"AI_IM_AI_FEISHU_ENABLED\": \"true\",\n \"AI_IM_AI_FEISHU_VERIFICATION_TOKEN\": \"dxZCfyFE816hLOrPbpE3pzdHJpmcXAMQ\",\n \"AI_IM_AI_FEISHU_ENCRYPT_KEY\": \"7qZik8wobG3FJVt8rrg97esIHfRRV6dc\"\n }\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-admin\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"dockerfilePath\": \"./frontend/Dockerfile\",\n \"containerPort\": 80,\n \"initialDelaySeconds\": 30,\n \"minNum\": 1,\n \"maxNum\": 3,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"envVariables\": {\n \"BACKEND_URL\": \"https://api.webyszl.cn\"\n }\n }\n },\n \"h5\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"fashion-h5\",\n \"servicePath\": \"/h5\",\n \"localPath\": \"./h5-web\",\n \"dockerfilePath\": \"./h5-web/Dockerfile\",\n \"containerPort\": 80,\n \"initialDelaySeconds\": 30,\n \"minNum\": 1,\n \"maxNum\": 3,\n \"cpu\": 0.25,\n \"mem\": 0.5\n }\n }\n }\n }\n}"}, "4d454c0ea46f46c64a4b5e6e9ead7cf0843b7890": {"t": 1785749955.7756293, "n": 2050, "h": "cef5193b6963e416", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"InitialDelaySeconds\": 300,\n \"PeriodSeconds\": 30,\n \"TimeoutSeconds\": 10,\n \"FailureThreshold\": 5,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"AGNES_API_KEY\": \"{{AGNES_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\",\n \"AI_IM_AI_FEISHU_ENABLED\": \"true\",\n \"AI_IM_AI_FEISHU_VERIFICATION_TOKEN\": \"dxZCfyFE816hLOrPbpE3pzdHJpmcXAMQ\",\n \"AI_IM_AI_FEISHU_ENCRYPT_KEY\": \"7qZik8wobG3FJVt8rrg97esIHfRRV6dc\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "9016c9bd9aad954ab25b55fac14ed0295cda5550": {"t": 1785749957.903767, "n": 1966, "h": "6f9aba996428b19a", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"InitialDelaySeconds\": 150,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"AGNES_API_KEY\": \"{{AGNES_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\",\n \"AI_IM_AI_FEISHU_ENABLED\": \"true\",\n \"AI_IM_AI_FEISHU_VERIFICATION_TOKEN\": \"dxZCfyFE816hLOrPbpE3pzdHJpmcXAMQ\",\n \"AI_IM_AI_FEISHU_ENCRYPT_KEY\": \"7qZik8wobG3FJVt8rrg97esIHfRRV6dc\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "49b31a363b2a1b237b97d53d638aad2dbcd3279f": {"t": 1785749959.2831678, "n": 1966, "h": "8b9aaf48e647f684", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"initialDelaySeconds\": 120,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"AGNES_API_KEY\": \"{{AGNES_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\",\n \"AI_IM_AI_FEISHU_ENABLED\": \"true\",\n \"AI_IM_AI_FEISHU_VERIFICATION_TOKEN\": \"dxZCfyFE816hLOrPbpE3pzdHJpmcXAMQ\",\n \"AI_IM_AI_FEISHU_ENCRYPT_KEY\": \"7qZik8wobG3FJVt8rrg97esIHfRRV6dc\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "45a0163991a4d9c2e1add3c194206884f6ecd03a": {"t": 1785749960.358283, "n": 1932, "h": "d8be46df05863e91", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"AGNES_API_KEY\": \"{{AGNES_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\",\n \"AI_IM_AI_FEISHU_ENABLED\": \"true\",\n \"AI_IM_AI_FEISHU_VERIFICATION_TOKEN\": \"dxZCfyFE816hLOrPbpE3pzdHJpmcXAMQ\",\n \"AI_IM_AI_FEISHU_ENCRYPT_KEY\": \"7qZik8wobG3FJVt8rrg97esIHfRRV6dc\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "fb7445b2abf78a0442d8fc132ec96513b2399050": {"t": 1785749961.8656561, "n": 1934, "h": "89ed0346fe79a2dd", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"DOUBAO_API_KEY\": \"{{DOUBAO_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\",\n \"AI_IM_AI_FEISHU_ENABLED\": \"true\",\n \"AI_IM_AI_FEISHU_VERIFICATION_TOKEN\": \"dxZCfyFE816hLOrPbpE3pzdHJpmcXAMQ\",\n \"AI_IM_AI_FEISHU_ENCRYPT_KEY\": \"7qZik8wobG3FJVt8rrg97esIHfRRV6dc\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "956c9f0e06bd2fa961a921da326c30029b68938a": {"t": 1785749962.9523864, "n": 1734, "h": "e1f979c607b4c7bb", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"DOUBAO_API_KEY\": \"{{DOUBAO_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\",\n \"AI_OPENAI_COMPAT_ENABLED\": \"{{AI_OPENAI_COMPAT_ENABLED}}\",\n \"AI_OPENAI_COMPAT_API_KEY\": \"{{AI_OPENAI_COMPAT_API_KEY}}\",\n \"AI_OPENAI_COMPAT_TENANT_ID\": \"{{AI_OPENAI_COMPAT_TENANT_ID}}\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "1ca8b8f7f9cdd4597b35367dcb6be1e89c77b1e4": {"t": 1785749964.0457792, "n": 1526, "h": "b89773a21c9bf00d", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"DOUBAO_API_KEY\": \"{{DOUBAO_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\",\n \"APP_SECURITY_PII_ENCRYPTION_KEY\": \"{{PII_ENCRYPTION_KEY}}\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "4893a2ef7839c1499308898109716c4f8ca8a213": {"t": 1785749969.022853, "n": 1457, "h": "52700e8c9a1d134d", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"DOUBAO_API_KEY\": \"{{DOUBAO_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_MP_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_MP_SECRET}}\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "c5ff9274b0de31b599715f7fc2aa76b4bf7f0c74": {"t": 1785749970.1310608, "n": 1355, "h": "327aacc25772a5d6", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\",\n \"DEEPSEEK_API_KEY\": \"{{DEEPSEEK_API_KEY}}\",\n \"DOUBAO_API_KEY\": \"{{DOUBAO_API_KEY}}\",\n \"VOYAGE_API_KEY\": \"{{VOYAGE_API_KEY}}\",\n \"QDRANT_URL\": \"{{QDRANT_URL}}\",\n \"QDRANT_API_KEY\": \"{{QDRANT_API_KEY}}\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "e701be03d01d12a792e286729420a3e2dadf973b": {"t": 1785749971.2254527, "n": 1119, "h": "f94db0707c47bd0e", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"WECHAT_TRUST_ALL_CERTS\": \"true\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "8cf66d2861f0cd29ad6f5cca6aed03fa700d0901": {"t": 1785749972.2951632, "n": 1077, "h": "e8ee40f14345cbb3", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8088,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "8cac5f58a452b3c354e16fc82c389f4912fe2ed5": {"t": 1785749973.5188007, "n": 1077, "h": "b6a839446273c8a3", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8080,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n },\n {\n \"name\": \"fashion-admin\",\n \"localPath\": \"./frontend/dist\",\n \"cloudPath\": \"/\"\n }\n ]\n}\n"}, "65e5b81123568f44fc9098a0c003378f12852fe2": {"t": 1785749974.6060681, "n": 972, "h": "59e63bd1ef0c6267", "c": "{\n \"version\": \"2.0\",\n \"envId\": \"{{TCB_ENV_ID}}\",\n \"services\": [\n {\n \"name\": \"fashion-backend\",\n \"dockerfile\": \"./backend/Dockerfile\",\n \"containerPort\": 8080,\n \"minNum\": 1,\n \"maxNum\": 5,\n \"cpu\": 1,\n \"mem\": 2,\n \"docker\": {\n \"buildType\": \"Dockerfile\",\n \"dockerfilePath\": \"./backend/Dockerfile\"\n },\n \"envVariables\": {\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"APP_DB_HOST\": \"{{DB_HOST}}\",\n \"APP_DB_NAME\": \"{{DB_NAME}}\",\n \"APP_DB_USERNAME\": \"{{DB_USERNAME}}\",\n \"APP_DB_PASSWORD\": \"{{DB_PASSWORD}}\",\n \"APP_COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"APP_COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"APP_COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"APP_COS_REGION\": \"{{COS_REGION}}\"\n }\n }\n ],\n \"static\": [\n {\n \"name\": \"fashion-h5\",\n \"localPath\": \"./h5-web/dist\",\n \"cloudPath\": \"/h5\"\n }\n ]\n}\n"}, "bf0d8e1358f851dea63091cdab4e485562dd8725": {"t": 1785749975.6847663, "n": 2554, "h": "794b87bf8666262b", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"dgfbkffb.my-redis.j6jq9qgw.o7t59w4f.com\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"SERPAPI_ENABLED\": \"true\",\n \"QWEN_VL_API_KEY\": \"{{QWEN_VL_API_KEY}}\",\n \"DOUBAO_API_KEY\": \"{{DOUBAO_API_KEY}}\",\n \"APP_QRCODE_HMAC_SECRET\": \"{{QRCODE_HMAC_SECRET}}\",\n \"APP_QRCODE_REQUIRE_SIGNATURE\": \"true\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "94f4aebfc1a4ac8252fd599f34112b1d8933c02a": {"t": 1785749976.7376614, "n": 2573, "h": "7937ea1a9bed18ae", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"dgfbkffb.my-redis.j6jq9qgw.o7t59w4f.com\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"SERPAPI_ENABLED\": \"true\",\n \"QWEN_VL_API_KEY\": \"{{QWEN_VL_API_KEY}}\",\n \"DOUBAO_API_KEY\": \"7a8b0840-0a70-4fc1-a765-bb10c67beb06\",\n \"APP_QRCODE_HMAC_SECRET\": \"{{QRCODE_HMAC_SECRET}}\",\n \"APP_QRCODE_REQUIRE_SIGNATURE\": \"true\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "d74935f72791358c9b608ef2939d28f89c2f7fbd": {"t": 1785749977.7932262, "n": 2457, "h": "0b4e89fe05118e48", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"dgfbkffb.my-redis.j6jq9qgw.o7t59w4f.com\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"SERPAPI_ENABLED\": \"true\",\n \"QWEN_VL_API_KEY\": \"{{QWEN_VL_API_KEY}}\",\n \"DOUBAO_API_KEY\": \"7a8b0840-0a70-4fc1-a765-bb10c67beb06\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "029844acda5c091b5a594ea030aef7895aa831c6": {"t": 1785749978.875513, "n": 2387, "h": "4b14515dd791616a", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"dgfbkffb.my-redis.j6jq9qgw.o7t59w4f.com\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"SERPAPI_ENABLED\": \"true\",\n \"QWEN_VL_API_KEY\": \"{{QWEN_VL_API_KEY}}\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "fe45a6442ae73581b550b593691ff9808801d5a5": {"t": 1785749979.981822, "n": 2333, "h": "365d68f84ced12c2", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"dgfbkffb.my-redis.j6jq9qgw.o7t59w4f.com\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"SERPAPI_ENABLED\": \"true\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "8bb2076c82e219ecf8dcaa787165a03823b89348": {"t": 1785749981.0676723, "n": 2379, "h": "127277d1ca56b543", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"dgfbkffb.my-redis.j6jq9qgw.o7t59w4f.com\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\",\n \"SERPAPI_KEY\": \"{{SERPAPI_KEY}}\",\n \"SERPAPI_ENABLED\": \"true\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "b5a72b5c7060b6722ec3683f93a29968358e4a4b": {"t": 1785749982.104251, "n": 2294, "h": "c4158c5a28591e68", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"dgfbkffb.my-redis.j6jq9qgw.o7t59w4f.com\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "7e3b905db3795692be63c5757355ad74956e44be": {"t": 1785749983.2247484, "n": 2353, "h": "c71db57996def376", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"dgfbkffb.my-redis.j6jq9qgw.o7t59w4f.com\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"SPRING_REDIS_PASSWORD\": \"{{REDIS_PASSWORD}}\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "2eb2c092df42834f13ba9748da605aacf77d750a": {"t": 1785749984.2849438, "n": 2269, "h": "bd39884af4741658", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&connectTimeout=5000&socketTimeout=10000&tcpKeepAlive=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"{{REDIS_HOST}}\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "ad51ee6c6a3f767ca4208e39dffd66fb54532203": {"t": 1785749985.343798, "n": 2211, "h": "67574c62768cce8f", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"{{REDIS_HOST}}\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"SPRING_PROFILES_ACTIVE\": \"prod\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "601715e5cbc4c33900ddcd2a3df14884eaf406b0": {"t": 1785749986.407472, "n": 2165, "h": "8cf96518164031d4", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"{{REDIS_HOST}}\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health/liveness\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "40ac69d8bb188db9ff3a7e76bcbef748accb06a1": {"t": 1785749987.5192716, "n": 2156, "h": "2bfc268bdbb7fd0f", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"{{REDIS_HOST}}\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\",\n \"COS_SECRET_ID\": \"{{COS_SECRET_ID}}\",\n \"COS_SECRET_KEY\": \"{{COS_SECRET_KEY}}\",\n \"COS_REGION\": \"{{COS_REGION}}\",\n \"COS_BUCKET\": \"{{COS_BUCKET}}\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}, "270bc97bb3d0f62e5eded3e08752b3677c7c64af": {"t": 1785749988.746784, "n": 1966, "h": "c22de3d22a3029a3", "c": "{\n \"envId\": \"{{ENV_ID}}\",\n \"framework\": {\n \"name\": \"fashion-supplychain\",\n \"plugins\": {\n \"backend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"backend\",\n \"servicePath\": \"/backend\",\n \"localPath\": \"./backend\",\n \"containerPort\": 8088,\n \"cpu\": 2,\n \"mem\": 4,\n \"minNum\": 1,\n \"maxNum\": 4,\n \"policyType\": \"cpu\",\n \"policyThreshold\": 60,\n \"envVariables\": {\n \"PORT\": \"8088\",\n \"SPRING_DATASOURCE_URL\": \"jdbc:mysql://{{MYSQL_HOST}}:3306/fashion_supplychain?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true\",\n \"SPRING_DATASOURCE_USERNAME\": \"{{MYSQL_USER}}\",\n \"SPRING_DATASOURCE_PASSWORD\": \"{{MYSQL_PASSWORD}}\",\n \"SPRING_REDIS_HOST\": \"{{REDIS_HOST}}\",\n \"SPRING_REDIS_PORT\": \"6379\",\n \"APP_AUTH_JWT_SECRET\": \"{{JWT_SECRET}}\",\n \"WECHAT_MP_APPID\": \"{{WECHAT_APPID}}\",\n \"WECHAT_MP_SECRET\": \"{{WECHAT_SECRET}}\",\n \"WECHAT_MINI_PROGRAM_MOCK_ENABLED\": \"false\",\n \"FLYWAY_ENABLED\": \"true\",\n \"FASHION_DB_INITIALIZER_ENABLED\": \"false\",\n \"APP_CORS_ALLOWED_ORIGIN_PATTERNS\": \"https://*.tcloudbase.com,https://*.service.tcloudbase.com,https://*.ap-shanghai.service.tcloudbase.com\"\n },\n \"customLogs\": \"stdout\",\n \"healthPath\": \"/actuator/health\"\n }\n },\n \"frontend\": {\n \"use\": \"@cloudbase/framework-plugin-container\",\n \"inputs\": {\n \"serviceName\": \"frontend\",\n \"servicePath\": \"/\",\n \"localPath\": \"./frontend\",\n \"containerPort\": 80,\n \"cpu\": 0.5,\n \"mem\": 1,\n \"minNum\": 1,\n \"maxNum\": 2,\n \"envVariables\": {\n \"BACKEND_URL\": \"http://backend:8088\"\n }\n }\n }\n }\n }\n}\n"}}, "backend/src/main/java/com/fashion/supplychain/intelligence/gateway/AiInferenceRouter.java": {"92136382831779e22f23b72a08f4e137bfa17dd3": {"t": 1785745687.3106582, "n": 20750, "h": "31e72f3762438f68", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport com.fashion.supplychain.intelligence.service.CostExplosionGuard;\nimport com.fashion.supplychain.intelligence.service.ModelSelectionRouter;\nimport com.fashion.supplychain.common.UserContext;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\n@Slf4j\n@Service\n@Lazy\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n /**\n * 【P1-10修复】启动时校验 DEEPSEEK_API_KEY 非空。\n *\n * <p>原行为:key 为空时启动正常,但所有 AI 功能(对话/视觉/工具调用)都会失败,\n * 用户排查困难(错误散落在各 Service 中)。\n *\n * <p>现行为:\n * <ul>\n * <li>fail-fast-on-empty=true(默认 false):启动失败,明确提示缺失 key</li>\n * <li>fail-fast-on-empty=false(默认):启动成功,但打印 ERROR 级别告警 + 列出受影响功能</li>\n * </ul>\n *\n * <p>受影响功能:DeepSeek 对话、Spring AI 工具调用、AiAdvisor 日常建议、\n * SelfCritic 自评分、EvolutionPipeline 自进化、Qdrant 向量检索(embedding 降级)。\n */\n @Value(\"${ai.api-key.fail-fast-on-empty:false}\")\n private boolean failFastOnEmptyApiKey;\n\n @Value(\"${ai.deepseek.api-key:${DEEPSEEK_API_KEY:}}\")\n private String deepseekApiKey;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n @Autowired(required = false)\n private ModelSelectionRouter modelSelectionRouter;\n\n @Autowired(required = false)\n private CostExplosionGuard costExplosionGuard;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n /**\n * 【P1-10修复】启动时校验 DEEPSEEK_API_KEY。\n * 默认仅告警不阻止启动,开启 fail-fast-on-empty=true 可强制阻止启动。\n */\n @jakarta.annotation.PostConstruct\n public void validateApiKey() {\n boolean keyMissing = deepseekApiKey == null || deepseekApiKey.isBlank();\n if (!keyMissing) {\n log.info(\"[AiInferenceRouter] DEEPSEEK_API_KEY 已配置(长度={}),AI 功能正常可用\",\n deepseekApiKey.length());\n return;\n }\n\n String affectedFeatures = \"DeepSeek 对话 / Spring AI 工具调用 / AiAdvisor 日常建议 / \"\n + \"SelfCritic 自评分 / EvolutionPipeline 自进化 / Qdrant 向量检索(embedding 降级)\";\n\n if (failFastOnEmptyApiKey) {\n String msg = \"[AiInferenceRouter] DEEPSEEK_API_KEY 为空且 ai.api-key.fail-fast-on-empty=true,\"\n + \"应用启动被阻止。受影响功能:\" + affectedFeatures\n + \"。解决:设置环境变量 DEEPSEEK_API_KEY 或在 yml 配置 ai.deepseek.api-key\";\n log.error(msg);\n throw new IllegalStateException(msg);\n }\n\n log.error(\"[AiInferenceRouter] ⚠️ DEEPSEEK_API_KEY 为空!AI 功能将全部失败。受影响功能:{}\", affectedFeatures);\n log.error(\"[AiInferenceRouter] 解决:设置环境变量 DEEPSEEK_API_KEY 或在 yml 配置 ai.deepseek.api-key\");\n log.error(\"[AiInferenceRouter] 如需启动失败而非降级,设置 ai.api-key.fail-fast-on-empty=true\");\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatWithVision(String scene, String systemPrompt, String userMessage, String imageUrl) {\n // 视觉调用优先走 legacy adapter — Agnes视觉模型支持真正的image_url格式,\n // 比Spring AI适配器的文本嵌入方式更可靠\n if (legacyAdapter != null && legacyAdapter.isAvailable()) {\n try {\n IntelligenceInferenceResult result =\n legacyAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n if (result != null && result.isSuccess() && hasText(result.getContent())) {\n recordCostAndAudit(scene, result);\n return result;\n }\n log.warn(\"[AiInferenceRouter] legacy视觉调用未返回有效内容,fallback到Spring AI\");\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] legacy视觉调用异常,fallback到Spring AI: {}\", e.getMessage());\n }\n }\n // Spring AI 作为第二路径\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isAvailable()) {\n IntelligenceInferenceResult result = springAiAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n trackSpringAiHealth(result);\n recordCostAndAudit(scene, result);\n return result;\n }\n // 最后兜底返回空结果\n IntelligenceInferenceResult empty = new IntelligenceInferenceResult();\n empty.setSuccess(false);\n empty.setProvider(\"none\");\n empty.setErrorMessage(\"没有可用的视觉模型(请配置AGNES_API_KEY或DEEPSEEK_API_KEY)\");\n recordCostAndAudit(scene, empty);\n return empty;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public boolean isVisionAvailable() {\n if (legacyAdapter != null && legacyAdapter.isVisionAvailable()) {\n return true;\n }\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isVisionAvailable()) {\n return true;\n }\n return false;\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n if (prompt == null || prompt.isBlank()) {\n log.warn(\"[AiInferenceRouter] chatSimple 收到空 prompt,跳过 LLM 调用\");\n return \"\";\n }\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 带模型选择的聊天接口实现(per-call model selection)。\n *\n * <p>路由到具体的底层 gateway(SpringAiInferenceAdapter / LegacyInferenceAdapter),\n * 由底层 adapter 真正实现 per-call model 覆盖。\n * 同时记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御)。\n */\n @Override\n public String chatWithModel(String prompt, Long tenantId, Long userId, String modelId) {\n long start = System.currentTimeMillis();\n AiInferenceGateway gateway = resolveGateway(\"model-selection\");\n String content;\n try {\n content = gateway.chatWithModel(prompt, tenantId, userId, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModel failed, fallback to legacy: {}\", e.getMessage());\n content = legacyAdapter.chatWithModel(prompt, tenantId, userId, modelId);\n }\n long elapsed = System.currentTimeMillis() - start;\n\n // 记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御 + 重复检测)\n if (costExplosionGuard != null && tenantId != null) {\n try {\n // 用 \"llm_call\" 作为 toolName,prompt 哈希作为 paramsHash\n String paramsHash = costExplosionGuard.hashParams(prompt);\n costExplosionGuard.recordToolCall(tenantId, \"llm_call\", paramsHash,\n content != null ? content : \"\");\n log.debug(\"[AiInferenceRouter] LLM 调用成本已记录 tenantId={} modelId={} elapsed={}ms\",\n tenantId, modelId, elapsed);\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] 记录 LLM 调用成本失败(不影响主流程): {}\", e.getMessage());\n }\n }\n\n log.info(\"[AiInferenceRouter] chatWithModel: gateway={} modelId={} elapsed={}ms contentLen={}\",\n gateway.getProviderName(), modelId != null ? modelId : \"default\",\n elapsed, content != null ? content.length() : 0);\n return content;\n }\n\n /**\n * Per-call 模型选择调用(借鉴 Claude Agent SDK per-call model selection)。\n *\n * <p>根据用户消息复杂度、预估工具调用数、多域标识自动选择模型分级:\n * <ul>\n * <li>ECONOMY — 简单查询 → 便宜模型</li>\n * <li>STANDARD — 普通对话 → 标准模型</li>\n * <li>PREMIUM — 复杂排产 → 强模型</li>\n * </ul>\n *\n * <p>★ 接入点3:真正调用 gateway.chatWithModel 传递 modelId,而非仅用 scene 路由。\n *\n * @param prompt 完整提示词\n * @param userMessage 用户原始消息(用于复杂度评估)\n * @param estimatedToolCalls 预估工具调用数\n * @param isMultiDomain 是否多域任务\n * @return LLM 回答文本\n */\n public String chatWithModelSelection(String prompt, String userMessage,\n int estimatedToolCalls, boolean isMultiDomain) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n ModelSelectionRouter.ModelTier tier = modelSelectionRouter.selectModel(\n userMessage, estimatedToolCalls, isMultiDomain);\n String modelId = modelSelectionRouter.resolveModelId(tier);\n log.info(\"[AiInferenceRouter] per-call model selection: tier={} modelId={}\", tier, modelId);\n\n // 从 UserContext 获取 tenantId/userId(多租户隔离 + 成本追踪)\n Long tenantId = UserContext.tenantId();\n String userIdStr = UserContext.userId();\n\n // ★ 真正调用 gateway.chatWithModel 传递 modelId(接入点3 核心)\n try {\n return chatWithModel(prompt, tenantId, userIdStr != null ? userIdStr.hashCode() : 0L, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModelSelection failed, fallback to chatSimple: {}\", e.getMessage());\n return chatSimple(prompt);\n }\n }\n\n /**\n * 强制使用 PREMIUM 模型调用(高风险场景,如复杂排产优化)。\n *\n * @param prompt 完整提示词\n * @return LLM 回答文本\n */\n public String chatPremium(String prompt) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n String modelId = modelSelectionRouter.resolveModelId(ModelSelectionRouter.ModelTier.PREMIUM);\n log.info(\"[AiInferenceRouter] forced premium model: modelId={}\", modelId);\n IntelligenceInferenceResult result = chat(\"premium-reasoning\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /** 根据模型分级映射到 scene(用于网关路由 hint) */\n private String resolveSceneByTier(ModelSelectionRouter.ModelTier tier) {\n return switch (tier) {\n case ECONOMY -> \"economy-query\";\n case STANDARD -> \"reflection\";\n case PREMIUM -> \"premium-reasoning\";\n };\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n\n private boolean hasText(String value) {\n return value != null && !value.trim().isEmpty();\n }\n}\n"}, "92b7fd9578d448a1537b900a5ee7fb0da73abc84": {"t": 1785749954.3668191, "n": 20750, "h": "31e72f3762438f68", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport com.fashion.supplychain.intelligence.service.CostExplosionGuard;\nimport com.fashion.supplychain.intelligence.service.ModelSelectionRouter;\nimport com.fashion.supplychain.common.UserContext;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\n@Slf4j\n@Service\n@Lazy\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n /**\n * 【P1-10修复】启动时校验 DEEPSEEK_API_KEY 非空。\n *\n * <p>原行为:key 为空时启动正常,但所有 AI 功能(对话/视觉/工具调用)都会失败,\n * 用户排查困难(错误散落在各 Service 中)。\n *\n * <p>现行为:\n * <ul>\n * <li>fail-fast-on-empty=true(默认 false):启动失败,明确提示缺失 key</li>\n * <li>fail-fast-on-empty=false(默认):启动成功,但打印 ERROR 级别告警 + 列出受影响功能</li>\n * </ul>\n *\n * <p>受影响功能:DeepSeek 对话、Spring AI 工具调用、AiAdvisor 日常建议、\n * SelfCritic 自评分、EvolutionPipeline 自进化、Qdrant 向量检索(embedding 降级)。\n */\n @Value(\"${ai.api-key.fail-fast-on-empty:false}\")\n private boolean failFastOnEmptyApiKey;\n\n @Value(\"${ai.deepseek.api-key:${DEEPSEEK_API_KEY:}}\")\n private String deepseekApiKey;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n @Autowired(required = false)\n private ModelSelectionRouter modelSelectionRouter;\n\n @Autowired(required = false)\n private CostExplosionGuard costExplosionGuard;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n /**\n * 【P1-10修复】启动时校验 DEEPSEEK_API_KEY。\n * 默认仅告警不阻止启动,开启 fail-fast-on-empty=true 可强制阻止启动。\n */\n @jakarta.annotation.PostConstruct\n public void validateApiKey() {\n boolean keyMissing = deepseekApiKey == null || deepseekApiKey.isBlank();\n if (!keyMissing) {\n log.info(\"[AiInferenceRouter] DEEPSEEK_API_KEY 已配置(长度={}),AI 功能正常可用\",\n deepseekApiKey.length());\n return;\n }\n\n String affectedFeatures = \"DeepSeek 对话 / Spring AI 工具调用 / AiAdvisor 日常建议 / \"\n + \"SelfCritic 自评分 / EvolutionPipeline 自进化 / Qdrant 向量检索(embedding 降级)\";\n\n if (failFastOnEmptyApiKey) {\n String msg = \"[AiInferenceRouter] DEEPSEEK_API_KEY 为空且 ai.api-key.fail-fast-on-empty=true,\"\n + \"应用启动被阻止。受影响功能:\" + affectedFeatures\n + \"。解决:设置环境变量 DEEPSEEK_API_KEY 或在 yml 配置 ai.deepseek.api-key\";\n log.error(msg);\n throw new IllegalStateException(msg);\n }\n\n log.error(\"[AiInferenceRouter] ⚠️ DEEPSEEK_API_KEY 为空!AI 功能将全部失败。受影响功能:{}\", affectedFeatures);\n log.error(\"[AiInferenceRouter] 解决:设置环境变量 DEEPSEEK_API_KEY 或在 yml 配置 ai.deepseek.api-key\");\n log.error(\"[AiInferenceRouter] 如需启动失败而非降级,设置 ai.api-key.fail-fast-on-empty=true\");\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatWithVision(String scene, String systemPrompt, String userMessage, String imageUrl) {\n // 视觉调用优先走 legacy adapter — Agnes视觉模型支持真正的image_url格式,\n // 比Spring AI适配器的文本嵌入方式更可靠\n if (legacyAdapter != null && legacyAdapter.isAvailable()) {\n try {\n IntelligenceInferenceResult result =\n legacyAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n if (result != null && result.isSuccess() && hasText(result.getContent())) {\n recordCostAndAudit(scene, result);\n return result;\n }\n log.warn(\"[AiInferenceRouter] legacy视觉调用未返回有效内容,fallback到Spring AI\");\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] legacy视觉调用异常,fallback到Spring AI: {}\", e.getMessage());\n }\n }\n // Spring AI 作为第二路径\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isAvailable()) {\n IntelligenceInferenceResult result = springAiAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n trackSpringAiHealth(result);\n recordCostAndAudit(scene, result);\n return result;\n }\n // 最后兜底返回空结果\n IntelligenceInferenceResult empty = new IntelligenceInferenceResult();\n empty.setSuccess(false);\n empty.setProvider(\"none\");\n empty.setErrorMessage(\"没有可用的视觉模型(请配置AGNES_API_KEY或DEEPSEEK_API_KEY)\");\n recordCostAndAudit(scene, empty);\n return empty;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public boolean isVisionAvailable() {\n if (legacyAdapter != null && legacyAdapter.isVisionAvailable()) {\n return true;\n }\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isVisionAvailable()) {\n return true;\n }\n return false;\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n if (prompt == null || prompt.isBlank()) {\n log.warn(\"[AiInferenceRouter] chatSimple 收到空 prompt,跳过 LLM 调用\");\n return \"\";\n }\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 带模型选择的聊天接口实现(per-call model selection)。\n *\n * <p>路由到具体的底层 gateway(SpringAiInferenceAdapter / LegacyInferenceAdapter),\n * 由底层 adapter 真正实现 per-call model 覆盖。\n * 同时记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御)。\n */\n @Override\n public String chatWithModel(String prompt, Long tenantId, Long userId, String modelId) {\n long start = System.currentTimeMillis();\n AiInferenceGateway gateway = resolveGateway(\"model-selection\");\n String content;\n try {\n content = gateway.chatWithModel(prompt, tenantId, userId, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModel failed, fallback to legacy: {}\", e.getMessage());\n content = legacyAdapter.chatWithModel(prompt, tenantId, userId, modelId);\n }\n long elapsed = System.currentTimeMillis() - start;\n\n // 记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御 + 重复检测)\n if (costExplosionGuard != null && tenantId != null) {\n try {\n // 用 \"llm_call\" 作为 toolName,prompt 哈希作为 paramsHash\n String paramsHash = costExplosionGuard.hashParams(prompt);\n costExplosionGuard.recordToolCall(tenantId, \"llm_call\", paramsHash,\n content != null ? content : \"\");\n log.debug(\"[AiInferenceRouter] LLM 调用成本已记录 tenantId={} modelId={} elapsed={}ms\",\n tenantId, modelId, elapsed);\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] 记录 LLM 调用成本失败(不影响主流程): {}\", e.getMessage());\n }\n }\n\n log.info(\"[AiInferenceRouter] chatWithModel: gateway={} modelId={} elapsed={}ms contentLen={}\",\n gateway.getProviderName(), modelId != null ? modelId : \"default\",\n elapsed, content != null ? content.length() : 0);\n return content;\n }\n\n /**\n * Per-call 模型选择调用(借鉴 Claude Agent SDK per-call model selection)。\n *\n * <p>根据用户消息复杂度、预估工具调用数、多域标识自动选择模型分级:\n * <ul>\n * <li>ECONOMY — 简单查询 → 便宜模型</li>\n * <li>STANDARD — 普通对话 → 标准模型</li>\n * <li>PREMIUM — 复杂排产 → 强模型</li>\n * </ul>\n *\n * <p>★ 接入点3:真正调用 gateway.chatWithModel 传递 modelId,而非仅用 scene 路由。\n *\n * @param prompt 完整提示词\n * @param userMessage 用户原始消息(用于复杂度评估)\n * @param estimatedToolCalls 预估工具调用数\n * @param isMultiDomain 是否多域任务\n * @return LLM 回答文本\n */\n public String chatWithModelSelection(String prompt, String userMessage,\n int estimatedToolCalls, boolean isMultiDomain) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n ModelSelectionRouter.ModelTier tier = modelSelectionRouter.selectModel(\n userMessage, estimatedToolCalls, isMultiDomain);\n String modelId = modelSelectionRouter.resolveModelId(tier);\n log.info(\"[AiInferenceRouter] per-call model selection: tier={} modelId={}\", tier, modelId);\n\n // 从 UserContext 获取 tenantId/userId(多租户隔离 + 成本追踪)\n Long tenantId = UserContext.tenantId();\n String userIdStr = UserContext.userId();\n\n // ★ 真正调用 gateway.chatWithModel 传递 modelId(接入点3 核心)\n try {\n return chatWithModel(prompt, tenantId, userIdStr != null ? userIdStr.hashCode() : 0L, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModelSelection failed, fallback to chatSimple: {}\", e.getMessage());\n return chatSimple(prompt);\n }\n }\n\n /**\n * 强制使用 PREMIUM 模型调用(高风险场景,如复杂排产优化)。\n *\n * @param prompt 完整提示词\n * @return LLM 回答文本\n */\n public String chatPremium(String prompt) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n String modelId = modelSelectionRouter.resolveModelId(ModelSelectionRouter.ModelTier.PREMIUM);\n log.info(\"[AiInferenceRouter] forced premium model: modelId={}\", modelId);\n IntelligenceInferenceResult result = chat(\"premium-reasoning\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /** 根据模型分级映射到 scene(用于网关路由 hint) */\n private String resolveSceneByTier(ModelSelectionRouter.ModelTier tier) {\n return switch (tier) {\n case ECONOMY -> \"economy-query\";\n case STANDARD -> \"reflection\";\n case PREMIUM -> \"premium-reasoning\";\n };\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n\n private boolean hasText(String value) {\n return value != null && !value.trim().isEmpty();\n }\n}\n"}, "6fe4a586649c80cfffa2ec60afd3daca815705ed": {"t": 1785749955.5275457, "n": 18783, "h": "d1c9ce85273937de", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport com.fashion.supplychain.intelligence.service.CostExplosionGuard;\nimport com.fashion.supplychain.intelligence.service.ModelSelectionRouter;\nimport com.fashion.supplychain.common.UserContext;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\n@Slf4j\n@Service\n@Lazy\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n @Autowired(required = false)\n private ModelSelectionRouter modelSelectionRouter;\n\n @Autowired(required = false)\n private CostExplosionGuard costExplosionGuard;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatWithVision(String scene, String systemPrompt, String userMessage, String imageUrl) {\n // 视觉调用优先走 legacy adapter — Agnes视觉模型支持真正的image_url格式,\n // 比Spring AI适配器的文本嵌入方式更可靠\n if (legacyAdapter != null && legacyAdapter.isAvailable()) {\n try {\n IntelligenceInferenceResult result =\n legacyAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n if (result != null && result.isSuccess() && hasText(result.getContent())) {\n recordCostAndAudit(scene, result);\n return result;\n }\n log.warn(\"[AiInferenceRouter] legacy视觉调用未返回有效内容,fallback到Spring AI\");\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] legacy视觉调用异常,fallback到Spring AI: {}\", e.getMessage());\n }\n }\n // Spring AI 作为第二路径\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isAvailable()) {\n IntelligenceInferenceResult result = springAiAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n trackSpringAiHealth(result);\n recordCostAndAudit(scene, result);\n return result;\n }\n // 最后兜底返回空结果\n IntelligenceInferenceResult empty = new IntelligenceInferenceResult();\n empty.setSuccess(false);\n empty.setProvider(\"none\");\n empty.setErrorMessage(\"没有可用的视觉模型(请配置AGNES_API_KEY或DEEPSEEK_API_KEY)\");\n recordCostAndAudit(scene, empty);\n return empty;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public boolean isVisionAvailable() {\n if (legacyAdapter != null && legacyAdapter.isVisionAvailable()) {\n return true;\n }\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isVisionAvailable()) {\n return true;\n }\n return false;\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n if (prompt == null || prompt.isBlank()) {\n log.warn(\"[AiInferenceRouter] chatSimple 收到空 prompt,跳过 LLM 调用\");\n return \"\";\n }\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 带模型选择的聊天接口实现(per-call model selection)。\n *\n * <p>路由到具体的底层 gateway(SpringAiInferenceAdapter / LegacyInferenceAdapter),\n * 由底层 adapter 真正实现 per-call model 覆盖。\n * 同时记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御)。\n */\n @Override\n public String chatWithModel(String prompt, Long tenantId, Long userId, String modelId) {\n long start = System.currentTimeMillis();\n AiInferenceGateway gateway = resolveGateway(\"model-selection\");\n String content;\n try {\n content = gateway.chatWithModel(prompt, tenantId, userId, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModel failed, fallback to legacy: {}\", e.getMessage());\n content = legacyAdapter.chatWithModel(prompt, tenantId, userId, modelId);\n }\n long elapsed = System.currentTimeMillis() - start;\n\n // 记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御 + 重复检测)\n if (costExplosionGuard != null && tenantId != null) {\n try {\n // 用 \"llm_call\" 作为 toolName,prompt 哈希作为 paramsHash\n String paramsHash = costExplosionGuard.hashParams(prompt);\n costExplosionGuard.recordToolCall(tenantId, \"llm_call\", paramsHash,\n content != null ? content : \"\");\n log.debug(\"[AiInferenceRouter] LLM 调用成本已记录 tenantId={} modelId={} elapsed={}ms\",\n tenantId, modelId, elapsed);\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] 记录 LLM 调用成本失败(不影响主流程): {}\", e.getMessage());\n }\n }\n\n log.info(\"[AiInferenceRouter] chatWithModel: gateway={} modelId={} elapsed={}ms contentLen={}\",\n gateway.getProviderName(), modelId != null ? modelId : \"default\",\n elapsed, content != null ? content.length() : 0);\n return content;\n }\n\n /**\n * Per-call 模型选择调用(借鉴 Claude Agent SDK per-call model selection)。\n *\n * <p>根据用户消息复杂度、预估工具调用数、多域标识自动选择模型分级:\n * <ul>\n * <li>ECONOMY — 简单查询 → 便宜模型</li>\n * <li>STANDARD — 普通对话 → 标准模型</li>\n * <li>PREMIUM — 复杂排产 → 强模型</li>\n * </ul>\n *\n * <p>★ 接入点3:真正调用 gateway.chatWithModel 传递 modelId,而非仅用 scene 路由。\n *\n * @param prompt 完整提示词\n * @param userMessage 用户原始消息(用于复杂度评估)\n * @param estimatedToolCalls 预估工具调用数\n * @param isMultiDomain 是否多域任务\n * @return LLM 回答文本\n */\n public String chatWithModelSelection(String prompt, String userMessage,\n int estimatedToolCalls, boolean isMultiDomain) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n ModelSelectionRouter.ModelTier tier = modelSelectionRouter.selectModel(\n userMessage, estimatedToolCalls, isMultiDomain);\n String modelId = modelSelectionRouter.resolveModelId(tier);\n log.info(\"[AiInferenceRouter] per-call model selection: tier={} modelId={}\", tier, modelId);\n\n // 从 UserContext 获取 tenantId/userId(多租户隔离 + 成本追踪)\n Long tenantId = UserContext.tenantId();\n String userIdStr = UserContext.userId();\n\n // ★ 真正调用 gateway.chatWithModel 传递 modelId(接入点3 核心)\n try {\n return chatWithModel(prompt, tenantId, userIdStr != null ? userIdStr.hashCode() : 0L, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModelSelection failed, fallback to chatSimple: {}\", e.getMessage());\n return chatSimple(prompt);\n }\n }\n\n /**\n * 强制使用 PREMIUM 模型调用(高风险场景,如复杂排产优化)。\n *\n * @param prompt 完整提示词\n * @return LLM 回答文本\n */\n public String chatPremium(String prompt) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n String modelId = modelSelectionRouter.resolveModelId(ModelSelectionRouter.ModelTier.PREMIUM);\n log.info(\"[AiInferenceRouter] forced premium model: modelId={}\", modelId);\n IntelligenceInferenceResult result = chat(\"premium-reasoning\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /** 根据模型分级映射到 scene(用于网关路由 hint) */\n private String resolveSceneByTier(ModelSelectionRouter.ModelTier tier) {\n return switch (tier) {\n case ECONOMY -> \"economy-query\";\n case STANDARD -> \"reflection\";\n case PREMIUM -> \"premium-reasoning\";\n };\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n\n private boolean hasText(String value) {\n return value != null && !value.trim().isEmpty();\n }\n}\n"}, "0fc1de77cd3cb0bcbf5f816d748f22c7ffbf316f": {"t": 1785749956.6754286, "n": 18449, "h": "15ced3ba90c9e71c", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport com.fashion.supplychain.intelligence.service.CostExplosionGuard;\nimport com.fashion.supplychain.intelligence.service.ModelSelectionRouter;\nimport com.fashion.supplychain.common.UserContext;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\n@Slf4j\n@Service\n@Lazy\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n @Autowired(required = false)\n private ModelSelectionRouter modelSelectionRouter;\n\n @Autowired(required = false)\n private CostExplosionGuard costExplosionGuard;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatWithVision(String scene, String systemPrompt, String userMessage, String imageUrl) {\n // 视觉调用优先走 legacy adapter — Agnes视觉模型支持真正的image_url格式,\n // 比Spring AI适配器的文本嵌入方式更可靠\n if (legacyAdapter != null && legacyAdapter.isAvailable()) {\n try {\n IntelligenceInferenceResult result =\n legacyAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n if (result != null && result.isSuccess() && hasText(result.getContent())) {\n recordCostAndAudit(scene, result);\n return result;\n }\n log.warn(\"[AiInferenceRouter] legacy视觉调用未返回有效内容,fallback到Spring AI\");\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] legacy视觉调用异常,fallback到Spring AI: {}\", e.getMessage());\n }\n }\n // Spring AI 作为第二路径\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isAvailable()) {\n IntelligenceInferenceResult result = springAiAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n trackSpringAiHealth(result);\n recordCostAndAudit(scene, result);\n return result;\n }\n // 最后兜底返回空结果\n IntelligenceInferenceResult empty = new IntelligenceInferenceResult();\n empty.setSuccess(false);\n empty.setProvider(\"none\");\n empty.setErrorMessage(\"没有可用的视觉模型(请配置AGNES_API_KEY或DEEPSEEK_API_KEY)\");\n recordCostAndAudit(scene, empty);\n return empty;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n if (prompt == null || prompt.isBlank()) {\n log.warn(\"[AiInferenceRouter] chatSimple 收到空 prompt,跳过 LLM 调用\");\n return \"\";\n }\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 带模型选择的聊天接口实现(per-call model selection)。\n *\n * <p>路由到具体的底层 gateway(SpringAiInferenceAdapter / LegacyInferenceAdapter),\n * 由底层 adapter 真正实现 per-call model 覆盖。\n * 同时记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御)。\n */\n @Override\n public String chatWithModel(String prompt, Long tenantId, Long userId, String modelId) {\n long start = System.currentTimeMillis();\n AiInferenceGateway gateway = resolveGateway(\"model-selection\");\n String content;\n try {\n content = gateway.chatWithModel(prompt, tenantId, userId, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModel failed, fallback to legacy: {}\", e.getMessage());\n content = legacyAdapter.chatWithModel(prompt, tenantId, userId, modelId);\n }\n long elapsed = System.currentTimeMillis() - start;\n\n // 记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御 + 重复检测)\n if (costExplosionGuard != null && tenantId != null) {\n try {\n // 用 \"llm_call\" 作为 toolName,prompt 哈希作为 paramsHash\n String paramsHash = costExplosionGuard.hashParams(prompt);\n costExplosionGuard.recordToolCall(tenantId, \"llm_call\", paramsHash,\n content != null ? content : \"\");\n log.debug(\"[AiInferenceRouter] LLM 调用成本已记录 tenantId={} modelId={} elapsed={}ms\",\n tenantId, modelId, elapsed);\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] 记录 LLM 调用成本失败(不影响主流程): {}\", e.getMessage());\n }\n }\n\n log.info(\"[AiInferenceRouter] chatWithModel: gateway={} modelId={} elapsed={}ms contentLen={}\",\n gateway.getProviderName(), modelId != null ? modelId : \"default\",\n elapsed, content != null ? content.length() : 0);\n return content;\n }\n\n /**\n * Per-call 模型选择调用(借鉴 Claude Agent SDK per-call model selection)。\n *\n * <p>根据用户消息复杂度、预估工具调用数、多域标识自动选择模型分级:\n * <ul>\n * <li>ECONOMY — 简单查询 → 便宜模型</li>\n * <li>STANDARD — 普通对话 → 标准模型</li>\n * <li>PREMIUM — 复杂排产 → 强模型</li>\n * </ul>\n *\n * <p>★ 接入点3:真正调用 gateway.chatWithModel 传递 modelId,而非仅用 scene 路由。\n *\n * @param prompt 完整提示词\n * @param userMessage 用户原始消息(用于复杂度评估)\n * @param estimatedToolCalls 预估工具调用数\n * @param isMultiDomain 是否多域任务\n * @return LLM 回答文本\n */\n public String chatWithModelSelection(String prompt, String userMessage,\n int estimatedToolCalls, boolean isMultiDomain) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n ModelSelectionRouter.ModelTier tier = modelSelectionRouter.selectModel(\n userMessage, estimatedToolCalls, isMultiDomain);\n String modelId = modelSelectionRouter.resolveModelId(tier);\n log.info(\"[AiInferenceRouter] per-call model selection: tier={} modelId={}\", tier, modelId);\n\n // 从 UserContext 获取 tenantId/userId(多租户隔离 + 成本追踪)\n Long tenantId = UserContext.tenantId();\n String userIdStr = UserContext.userId();\n\n // ★ 真正调用 gateway.chatWithModel 传递 modelId(接入点3 核心)\n try {\n return chatWithModel(prompt, tenantId, userIdStr != null ? userIdStr.hashCode() : 0L, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModelSelection failed, fallback to chatSimple: {}\", e.getMessage());\n return chatSimple(prompt);\n }\n }\n\n /**\n * 强制使用 PREMIUM 模型调用(高风险场景,如复杂排产优化)。\n *\n * @param prompt 完整提示词\n * @return LLM 回答文本\n */\n public String chatPremium(String prompt) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n String modelId = modelSelectionRouter.resolveModelId(ModelSelectionRouter.ModelTier.PREMIUM);\n log.info(\"[AiInferenceRouter] forced premium model: modelId={}\", modelId);\n IntelligenceInferenceResult result = chat(\"premium-reasoning\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /** 根据模型分级映射到 scene(用于网关路由 hint) */\n private String resolveSceneByTier(ModelSelectionRouter.ModelTier tier) {\n return switch (tier) {\n case ECONOMY -> \"economy-query\";\n case STANDARD -> \"reflection\";\n case PREMIUM -> \"premium-reasoning\";\n };\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n\n private boolean hasText(String value) {\n return value != null && !value.trim().isEmpty();\n }\n}\n"}, "dd96c9df2639c5b031479024e45d7cf36378fc96": {"t": 1785749957.8133483, "n": 18289, "h": "f37704f23d2abafb", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport com.fashion.supplychain.intelligence.service.CostExplosionGuard;\nimport com.fashion.supplychain.intelligence.service.ModelSelectionRouter;\nimport com.fashion.supplychain.common.UserContext;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\n@Slf4j\n@Service\n@Lazy\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n @Autowired(required = false)\n private ModelSelectionRouter modelSelectionRouter;\n\n @Autowired(required = false)\n private CostExplosionGuard costExplosionGuard;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatWithVision(String scene, String systemPrompt, String userMessage, String imageUrl) {\n // 视觉调用优先走 legacy adapter — Agnes视觉模型支持真正的image_url格式,\n // 比Spring AI适配器的文本嵌入方式更可靠\n if (legacyAdapter != null && legacyAdapter.isAvailable()) {\n try {\n IntelligenceInferenceResult result =\n legacyAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n if (result != null && result.isSuccess() && hasText(result.getContent())) {\n recordCostAndAudit(scene, result);\n return result;\n }\n log.warn(\"[AiInferenceRouter] legacy视觉调用未返回有效内容,fallback到Spring AI\");\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] legacy视觉调用异常,fallback到Spring AI: {}\", e.getMessage());\n }\n }\n // Spring AI 作为第二路径\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isAvailable()) {\n IntelligenceInferenceResult result = springAiAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n trackSpringAiHealth(result);\n recordCostAndAudit(scene, result);\n return result;\n }\n // 最后兜底返回空结果\n IntelligenceInferenceResult empty = new IntelligenceInferenceResult();\n empty.setSuccess(false);\n empty.setProvider(\"none\");\n empty.setErrorMessage(\"没有可用的视觉模型(请配置AGNES_API_KEY或DEEPSEEK_API_KEY)\");\n recordCostAndAudit(scene, empty);\n return empty;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 带模型选择的聊天接口实现(per-call model selection)。\n *\n * <p>路由到具体的底层 gateway(SpringAiInferenceAdapter / LegacyInferenceAdapter),\n * 由底层 adapter 真正实现 per-call model 覆盖。\n * 同时记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御)。\n */\n @Override\n public String chatWithModel(String prompt, Long tenantId, Long userId, String modelId) {\n long start = System.currentTimeMillis();\n AiInferenceGateway gateway = resolveGateway(\"model-selection\");\n String content;\n try {\n content = gateway.chatWithModel(prompt, tenantId, userId, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModel failed, fallback to legacy: {}\", e.getMessage());\n content = legacyAdapter.chatWithModel(prompt, tenantId, userId, modelId);\n }\n long elapsed = System.currentTimeMillis() - start;\n\n // 记录 LLM 调用成本到 CostExplosionGuard(用于成本爆炸防御 + 重复检测)\n if (costExplosionGuard != null && tenantId != null) {\n try {\n // 用 \"llm_call\" 作为 toolName,prompt 哈希作为 paramsHash\n String paramsHash = costExplosionGuard.hashParams(prompt);\n costExplosionGuard.recordToolCall(tenantId, \"llm_call\", paramsHash,\n content != null ? content : \"\");\n log.debug(\"[AiInferenceRouter] LLM 调用成本已记录 tenantId={} modelId={} elapsed={}ms\",\n tenantId, modelId, elapsed);\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] 记录 LLM 调用成本失败(不影响主流程): {}\", e.getMessage());\n }\n }\n\n log.info(\"[AiInferenceRouter] chatWithModel: gateway={} modelId={} elapsed={}ms contentLen={}\",\n gateway.getProviderName(), modelId != null ? modelId : \"default\",\n elapsed, content != null ? content.length() : 0);\n return content;\n }\n\n /**\n * Per-call 模型选择调用(借鉴 Claude Agent SDK per-call model selection)。\n *\n * <p>根据用户消息复杂度、预估工具调用数、多域标识自动选择模型分级:\n * <ul>\n * <li>ECONOMY — 简单查询 → 便宜模型</li>\n * <li>STANDARD — 普通对话 → 标准模型</li>\n * <li>PREMIUM — 复杂排产 → 强模型</li>\n * </ul>\n *\n * <p>★ 接入点3:真正调用 gateway.chatWithModel 传递 modelId,而非仅用 scene 路由。\n *\n * @param prompt 完整提示词\n * @param userMessage 用户原始消息(用于复杂度评估)\n * @param estimatedToolCalls 预估工具调用数\n * @param isMultiDomain 是否多域任务\n * @return LLM 回答文本\n */\n public String chatWithModelSelection(String prompt, String userMessage,\n int estimatedToolCalls, boolean isMultiDomain) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n ModelSelectionRouter.ModelTier tier = modelSelectionRouter.selectModel(\n userMessage, estimatedToolCalls, isMultiDomain);\n String modelId = modelSelectionRouter.resolveModelId(tier);\n log.info(\"[AiInferenceRouter] per-call model selection: tier={} modelId={}\", tier, modelId);\n\n // 从 UserContext 获取 tenantId/userId(多租户隔离 + 成本追踪)\n Long tenantId = UserContext.tenantId();\n String userIdStr = UserContext.userId();\n\n // ★ 真正调用 gateway.chatWithModel 传递 modelId(接入点3 核心)\n try {\n return chatWithModel(prompt, tenantId, userIdStr != null ? userIdStr.hashCode() : 0L, modelId);\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] chatWithModelSelection failed, fallback to chatSimple: {}\", e.getMessage());\n return chatSimple(prompt);\n }\n }\n\n /**\n * 强制使用 PREMIUM 模型调用(高风险场景,如复杂排产优化)。\n *\n * @param prompt 完整提示词\n * @return LLM 回答文本\n */\n public String chatPremium(String prompt) {\n if (modelSelectionRouter == null || !modelSelectionRouter.isEnabled()) {\n return chatSimple(prompt);\n }\n String modelId = modelSelectionRouter.resolveModelId(ModelSelectionRouter.ModelTier.PREMIUM);\n log.info(\"[AiInferenceRouter] forced premium model: modelId={}\", modelId);\n IntelligenceInferenceResult result = chat(\"premium-reasoning\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /** 根据模型分级映射到 scene(用于网关路由 hint) */\n private String resolveSceneByTier(ModelSelectionRouter.ModelTier tier) {\n return switch (tier) {\n case ECONOMY -> \"economy-query\";\n case STANDARD -> \"reflection\";\n case PREMIUM -> \"premium-reasoning\";\n };\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n\n private boolean hasText(String value) {\n return value != null && !value.trim().isEmpty();\n }\n}\n"}, "5096c8449e8798402e701944701dd0cc7033e1b7": {"t": 1785749959.105322, "n": 13376, "h": "ea0aae905f5cd560", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\n@Slf4j\n@Service\n@Lazy\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatWithVision(String scene, String systemPrompt, String userMessage, String imageUrl) {\n // 视觉调用优先走 legacy adapter — Agnes视觉模型支持真正的image_url格式,\n // 比Spring AI适配器的文本嵌入方式更可靠\n if (legacyAdapter != null && legacyAdapter.isAvailable()) {\n try {\n IntelligenceInferenceResult result =\n legacyAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n if (result != null && result.isSuccess() && hasText(result.getContent())) {\n recordCostAndAudit(scene, result);\n return result;\n }\n log.warn(\"[AiInferenceRouter] legacy视觉调用未返回有效内容,fallback到Spring AI\");\n } catch (Exception e) {\n log.warn(\"[AiInferenceRouter] legacy视觉调用异常,fallback到Spring AI: {}\", e.getMessage());\n }\n }\n // Spring AI 作为第二路径\n if (springAiAdapter != null && !isSpringAiCircuitOpen() && springAiAdapter.isAvailable()) {\n IntelligenceInferenceResult result = springAiAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n trackSpringAiHealth(result);\n recordCostAndAudit(scene, result);\n return result;\n }\n // 最后兜底返回空结果\n IntelligenceInferenceResult empty = new IntelligenceInferenceResult();\n empty.setSuccess(false);\n empty.setProvider(\"none\");\n empty.setErrorMessage(\"没有可用的视觉模型(请配置AGNES_API_KEY或DEEPSEEK_API_KEY)\");\n recordCostAndAudit(scene, empty);\n return empty;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n\n private boolean hasText(String value) {\n return value != null && !value.trim().isEmpty();\n }\n}\n"}, "fc10d435e3d8429461ad7e59b9cdd2179a093756": {"t": 1785749960.1908967, "n": 12396, "h": "f2fd10483f74da71", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\n@Slf4j\n@Service\n@Lazy\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatWithVision(String scene, String systemPrompt, String userMessage, String imageUrl) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n}\n"}, "90c4cf9babe331e23a41125a6d92eb5c22f87e67": {"t": 1785749962.3397975, "n": 12338, "h": "29bcfb47b5e08689", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatWithVision(String scene, String systemPrompt, String userMessage, String imageUrl) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatWithVision(scene, systemPrompt, userMessage, imageUrl);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n}\n"}, "8984df30bfe77bcc304b28856cde70285a8310aa": {"t": 1785749963.4729555, "n": 11590, "h": "dfc25be17d3265ed", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n public String chatSimple(String prompt) {\n IntelligenceInferenceResult result = chat(\"reflection\", null, prompt);\n return result != null ? result.getContent() : \"\";\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n}\n"}, "01152878ccaac8d964b541b9cfaad7850ed2f39a": {"t": 1785749969.0422065, "n": 11400, "h": "545ebcd37a4241d1", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断进入半开状态,允许探测请求\");\n return false;\n }\n return openSince > 0;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n if (springAiConsecutiveFailures.get() >= CIRCUIT_OPEN_THRESHOLD) {\n log.info(\"[AiInferenceRouter] Spring AI 半开探测成功,熔断恢复\");\n }\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures == CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.get() == 0) {\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后进入半开探测)\", failures, CIRCUIT_RESET_MS / 1000);\n } else if (springAiCircuitOpenSince.get() > 0\n && System.currentTimeMillis() - springAiCircuitOpenSince.get() > CIRCUIT_RESET_MS) {\n log.warn(\"[AiInferenceRouter] Spring AI 半开探测失败,重新熔断 {} 秒\", CIRCUIT_RESET_MS / 1000);\n springAiCircuitOpenSince.set(System.currentTimeMillis());\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-v4-flash\";\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n}\n"}, "79b8c850437e336d9253403986bf1d477c67887b": {"t": 1785749970.164954, "n": 10968, "h": "41acbf9c7acd51d6", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Autowired(required = false)\n private ModelConsortiumRouter modelConsortiumRouter;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n log.info(\"[AiInferenceRouter] Spring AI 熔断恢复({}秒冷却期已过)\", CIRCUIT_RESET_MS / 1000);\n return false;\n }\n return true;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures >= CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.compareAndSet(0, System.currentTimeMillis())) {\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后自动恢复尝试)\", failures, CIRCUIT_RESET_MS / 1000);\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n Map<String, Object> status = new java.util.LinkedHashMap<>();\n status.put(\"strategy\", routingStrategy);\n status.put(\"legacyAvailable\", legacyAdapter.isAvailable());\n status.put(\"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable());\n status.put(\"springAiCircuitOpen\", isSpringAiCircuitOpen());\n status.put(\"springAiConsecutiveFailures\", springAiConsecutiveFailures.get());\n status.put(\"activeProvider\", resolveGateway(null).getProviderName());\n if (modelConsortiumRouter != null) {\n status.put(\"consortium\", modelConsortiumRouter.getConfig());\n }\n return status;\n }\n\n /**\n * 模型智能路由 — 根据用户查询复杂度选择最优模型。\n * 可在上层(AiAgentOrchestrator)调用此方法获取推荐模型,传递给推理适配器。\n */\n public String recommendModel(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n return modelConsortiumRouter.selectModel(userMessage, hasImage, toolCount);\n }\n return \"deepseek-chat\"; // 默认\n }\n\n /**\n * 获取模型推荐参数(temperature、maxTokens、timeout)。\n */\n public ModelConsortiumRouter.ModelParams recommendModelParams(String userMessage, boolean hasImage, int toolCount) {\n if (modelConsortiumRouter != null) {\n ModelConsortiumRouter.Complexity complexity = modelConsortiumRouter.classifyComplexity(\n userMessage, hasImage, toolCount);\n return modelConsortiumRouter.getModelParams(complexity);\n }\n return new ModelConsortiumRouter.ModelParams(0.5, 1024, 30);\n }\n}\n"}, "ba4fdd20657f2c6fc9b5b95efce4d1123dd72f7a": {"t": 1785749971.2633975, "n": 9722, "h": "53ffd1286a57b186", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 3;\n private static final long CIRCUIT_RESET_MS = 30_000L;\n\n @Value(\"${ai.gateway.routing-strategy:failover}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover(scene);\n case \"legacy\" -> legacyAdapter;\n default -> resolveFailover(scene);\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover(String scene) {\n // 短查询/非Agent场景优先走 Spring AI(更快、工具调用更新)\n boolean preferSpringAi = isShortQueryScene(scene);\n if (preferSpringAi && !isSpringAiCircuitOpen()\n && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.debug(\"[AiInferenceRouter] 短查询场景={},优先使用 Spring AI\", scene);\n return springAiAdapter;\n }\n // Agent主循环优先Spring AI(流式+tool_calls更好),熔断时降级\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null) {\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isShortQueryScene(String scene) {\n return scene != null && (\n \"nl-intent\".equals(scene) || \"daily-brief\".equals(scene) ||\n \"critic_review\".equals(scene) || scene.startsWith(\"memory\"));\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n log.info(\"[AiInferenceRouter] Spring AI 熔断恢复({}秒冷却期已过)\", CIRCUIT_RESET_MS / 1000);\n return false;\n }\n return true;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures >= CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.compareAndSet(0, System.currentTimeMillis())) {\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后自动恢复尝试)\", failures, CIRCUIT_RESET_MS / 1000);\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n // 统一性能日志:方便排查双AI引擎表现\n log.info(\"[AiInference] engine={}, scene={}, success={}, latency={}ms, tokens={}/{}, fallback={}\",\n result.getProvider() != null ? result.getProvider() : \"unknown\",\n scene != null ? scene : \"default\",\n result.isSuccess(),\n result.getLatencyMs(),\n result.getPromptTokens(), result.getCompletionTokens(),\n Boolean.TRUE.equals(result.isFallbackUsed()));\n if (!result.isSuccess()) {\n log.warn(\"[AiInference] engine={}, scene={}, error={}\", result.getProvider(), scene, result.getErrorMessage());\n }\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n return Map.of(\n \"strategy\", routingStrategy,\n \"legacyAvailable\", legacyAdapter.isAvailable(),\n \"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable(),\n \"springAiCircuitOpen\", isSpringAiCircuitOpen(),\n \"springAiConsecutiveFailures\", springAiConsecutiveFailures.get(),\n \"activeProvider\", resolveGateway(null).getProviderName()\n );\n }\n}\n"}, "c40d5ea3a741db9d441460401c55605395c9f249": {"t": 1785749972.3551986, "n": 8367, "h": "cbc60b275b5af521", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n private static final int CIRCUIT_OPEN_THRESHOLD = 5;\n private static final long CIRCUIT_RESET_MS = 60_000L;\n\n @Value(\"${ai.gateway.routing-strategy:legacy}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n private final AtomicInteger springAiConsecutiveFailures = new AtomicInteger(0);\n private final AtomicLong springAiCircuitOpenSince = new AtomicLong(0);\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, systemPrompt, userMessage);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chat(scene, messages, tools);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n trackSpringAiHealth(result);\n if (isSpringAiCircuitOpen() && !\"legacy\".equals(gateway.getProviderName())) {\n log.info(\"[AiInferenceRouter] Spring AI 熔断触发,降级到 legacy 重试 scene={}\", scene);\n result = legacyAdapter.chatStream(scene, messages, tools, chunkConsumer);\n result.setFallbackUsed(true);\n }\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover();\n case \"legacy\" -> legacyAdapter;\n default -> legacyAdapter;\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (isSpringAiCircuitOpen()) {\n log.warn(\"[AiInferenceRouter] Spring AI 熔断中,降级到 legacy\");\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover() {\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (!isSpringAiCircuitOpen() && springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.warn(\"[AiInferenceRouter] legacy unavailable, using spring-ai as failover\");\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private boolean isSpringAiCircuitOpen() {\n if (springAiConsecutiveFailures.get() < CIRCUIT_OPEN_THRESHOLD) return false;\n long openSince = springAiCircuitOpenSince.get();\n if (openSince > 0 && System.currentTimeMillis() - openSince > CIRCUIT_RESET_MS) {\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n log.info(\"[AiInferenceRouter] Spring AI 熔断恢复({}秒冷却期已过)\", CIRCUIT_RESET_MS / 1000);\n return false;\n }\n return true;\n }\n\n private void trackSpringAiHealth(IntelligenceInferenceResult result) {\n if (result == null || !\"spring-ai\".equals(result.getProvider())) return;\n if (result.isSuccess()) {\n springAiConsecutiveFailures.set(0);\n springAiCircuitOpenSince.set(0);\n } else {\n int failures = springAiConsecutiveFailures.incrementAndGet();\n if (failures >= CIRCUIT_OPEN_THRESHOLD && springAiCircuitOpenSince.compareAndSet(0, System.currentTimeMillis())) {\n log.warn(\"[AiInferenceRouter] Spring AI 连续{}次失败,熔断开启({}秒后自动恢复尝试)\", failures, CIRCUIT_RESET_MS / 1000);\n }\n }\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n return Map.of(\n \"strategy\", routingStrategy,\n \"legacyAvailable\", legacyAdapter.isAvailable(),\n \"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable(),\n \"springAiCircuitOpen\", isSpringAiCircuitOpen(),\n \"springAiConsecutiveFailures\", springAiConsecutiveFailures.get(),\n \"activeProvider\", resolveGateway(null).getProviderName()\n );\n }\n}\n"}, "224b8fcea85eeb056d0d0a23657208a2ead7c393": {"t": 1785749974.4750092, "n": 5472, "h": "d7ab29a3a989ef56", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n @Value(\"${ai.gateway.routing-strategy:legacy}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover();\n case \"legacy\" -> legacyAdapter;\n default -> legacyAdapter;\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover() {\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.warn(\"[AiInferenceRouter] legacy unavailable, using spring-ai as failover\");\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n return Map.of(\n \"strategy\", routingStrategy,\n \"legacyAvailable\", legacyAdapter.isAvailable(),\n \"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable(),\n \"activeProvider\", resolveGateway(null).getProviderName()\n );\n }\n}\n"}, "9ff702bfd86152d27bcb7ab8788c73ff521c091b": {"t": 1785749975.545124, "n": 4441, "h": "17bf3af9d40deb40", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n @Value(\"${ai.gateway.routing-strategy:legacy}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return legacyAdapter;\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n return Map.of(\n \"strategy\", routingStrategy,\n \"legacyAvailable\", legacyAdapter.isAvailable(),\n \"springAiAvailable\", false,\n \"activeProvider\", legacyAdapter.getProviderName()\n );\n }\n}\n"}, "3e9c97d347fac7e1e28d906154ecb80603ffc273": {"t": 1785749976.6258533, "n": 5472, "h": "d7ab29a3a989ef56", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\n@Primary\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n @Value(\"${ai.gateway.routing-strategy:legacy}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover();\n case \"legacy\" -> legacyAdapter;\n default -> legacyAdapter;\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover() {\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.warn(\"[AiInferenceRouter] legacy unavailable, using spring-ai as failover\");\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n return Map.of(\n \"strategy\", routingStrategy,\n \"legacyAvailable\", legacyAdapter.isAvailable(),\n \"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable(),\n \"activeProvider\", resolveGateway(null).getProviderName()\n );\n }\n}\n"}, "1d8263154937b9e693cf726a990b9a264c4debef": {"t": 1785749977.6948547, "n": 5408, "h": "b7989e14d577f458", "c": "package com.fashion.supplychain.intelligence.gateway;\n\nimport com.fashion.supplychain.intelligence.agent.AiMessage;\nimport com.fashion.supplychain.intelligence.agent.AiTool;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.AiCostTrackingOrchestrator;\nimport com.fashion.supplychain.intelligence.orchestration.AiOperationAuditOrchestrator;\nimport java.util.List;\nimport java.util.Map;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\n@Slf4j\n@Service\npublic class AiInferenceRouter implements AiInferenceGateway {\n\n @Value(\"${ai.gateway.routing-strategy:legacy}\")\n private String routingStrategy;\n\n @Autowired\n private LegacyInferenceAdapter legacyAdapter;\n\n @Autowired(required = false)\n private SpringAiInferenceAdapter springAiAdapter;\n\n @Autowired(required = false)\n private AiCostTrackingOrchestrator aiCostTrackingOrchestrator;\n\n @Autowired(required = false)\n private AiOperationAuditOrchestrator aiOperationAuditOrchestrator;\n\n @Override\n public IntelligenceInferenceResult chat(String scene, String systemPrompt, String userMessage) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, systemPrompt, userMessage);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chat(String scene, List<AiMessage> messages, List<AiTool> tools) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chat(scene, messages, tools);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public IntelligenceInferenceResult chatStream(String scene, List<AiMessage> messages,\n List<AiTool> tools,\n StreamChunkConsumer chunkConsumer) {\n AiInferenceGateway gateway = resolveGateway(scene);\n IntelligenceInferenceResult result = gateway.chatStream(scene, messages, tools, chunkConsumer);\n recordCostAndAudit(scene, result);\n return result;\n }\n\n @Override\n public boolean isAvailable() {\n return resolveGateway(null).isAvailable();\n }\n\n @Override\n public String getProviderName() {\n return resolveGateway(null).getProviderName();\n }\n\n private AiInferenceGateway resolveGateway(String scene) {\n return switch (routingStrategy) {\n case \"spring-ai\" -> resolveSpringAi();\n case \"failover\" -> resolveFailover();\n case \"legacy\" -> legacyAdapter;\n default -> legacyAdapter;\n };\n }\n\n private AiInferenceGateway resolveSpringAi() {\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n return springAiAdapter;\n }\n log.warn(\"[AiInferenceRouter] spring-ai unavailable, falling back to legacy\");\n return legacyAdapter;\n }\n\n private AiInferenceGateway resolveFailover() {\n if (legacyAdapter.isAvailable()) {\n return legacyAdapter;\n }\n if (springAiAdapter != null && springAiAdapter.isAvailable()) {\n log.warn(\"[AiInferenceRouter] legacy unavailable, using spring-ai as failover\");\n return springAiAdapter;\n }\n return legacyAdapter;\n }\n\n private void recordCostAndAudit(String scene, IntelligenceInferenceResult result) {\n if (result == null) return;\n try {\n if (aiCostTrackingOrchestrator != null) {\n aiCostTrackingOrchestrator.recordAsync(\n result.getModel() != null ? result.getModel() : result.getProvider(),\n scene,\n result.getPromptTokens(),\n result.getCompletionTokens(),\n (int) result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] cost tracking failed: {}\", e.getMessage());\n }\n try {\n if (aiOperationAuditOrchestrator != null && result.getTraceId() != null) {\n aiOperationAuditOrchestrator.recordAudit(\n result.getTraceId(),\n \"inference:\" + scene,\n result.getPromptChars() + \" chars\",\n result.getResponseChars() + \" chars\",\n result.getLatencyMs(),\n result.isSuccess(),\n result.getErrorMessage()\n );\n }\n } catch (Exception e) {\n log.debug(\"[AiInferenceRouter] audit recording failed: {}\", e.getMessage());\n }\n }\n\n public Map<String, Object> getRoutingStatus() {\n return Map.of(\n \"strategy\", routingStrategy,\n \"legacyAvailable\", legacyAdapter.isAvailable(),\n \"springAiAvailable\", springAiAdapter != null && springAiAdapter.isAvailable(),\n \"activeProvider\", resolveGateway(null).getProviderName()\n );\n }\n}\n"}}, "backend/src/main/java/com/fashion/supplychain/intelligence/service/AiAdvisorService.java": {"92136382831779e22f23b72a08f4e137bfa17dd3": {"t": 1785745701.6783586, "n": 5838, "h": "0c462b9a466a0964", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.IntelligenceInferenceOrchestrator;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\nimport java.time.LocalDate;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * AI 顾问服务。\n *\n * <p>职责:保留配额与启用判断,对外提供 AI 顾问语义能力。\n * 真实推理由独立的 {@link IntelligenceInferenceOrchestrator} 执行。</p>\n *\n * <p>默认直连 DeepSeek,也支持通过 LiteLLM 网关接入,配置方式(环境变量):\n * <pre>\n * DEEPSEEK_API_KEY = sk-xxxxxxxxxxxxxxxx\n * AI_LITELLM_ENABLED = true/false\n * AI_LITELLM_BASE_URL = http://localhost:4000\n * AI_LITELLM_API_KEY = sk-virtual-key\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n */\n@Service\n@Lazy\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n /** 每个租户每天最多允许调用 DeepSeek 的次数(0 = 不限制) */\n @Value(\"${ai.deepseek.daily-quota-per-tenant:50}\")\n private int dailyQuotaPerTenant;\n\n @Autowired\n private IntelligenceInferenceOrchestrator intelligenceInferenceOrchestrator;\n\n /**\n * 每租户每日调用计数器\n * key = \"tenantId_yyyyMMdd\",value = 当日已调用次数\n * 不同日期的 key 自动过期(不再被读写),内存占用可忽略不计\n */\n private final ConcurrentHashMap<String, AtomicInteger> dailyCounters = new ConcurrentHashMap<>();\n\n @Scheduled(fixedRate = 3600000)\n public void cleanupStaleCounters() {\n String todaySuffix = \"_\" + LocalDate.now();\n dailyCounters.keySet().removeIf(key -> !key.endsWith(todaySuffix));\n }\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return intelligenceInferenceOrchestrator.isAnyModelEnabled();\n }\n\n /**\n * 检查当前租户今日配额是否已用完\n *\n * @param tenantId 租户ID(null 视为系统内部调用,不限制)\n * @return true = 配额充足可以调用;false = 今日配额已用完\n */\n public boolean checkAndConsumeQuota(Long tenantId) {\n if (dailyQuotaPerTenant <= 0 || tenantId == null) return true; // 0=不限\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.computeIfAbsent(key, k -> new AtomicInteger(0));\n int used = count.incrementAndGet();\n if (used > dailyQuotaPerTenant) {\n count.decrementAndGet(); // 回滚,不消耗\n log.warn(\"[AiAdvisor] 租户 {} 今日AI调用已达配额上限 {}\", tenantId, dailyQuotaPerTenant);\n return false;\n }\n log.debug(\"[AiAdvisor] 租户 {} 今日AI调用 {}/{}\", tenantId, used, dailyQuotaPerTenant);\n return true;\n }\n\n /**\n * 查询当前租户今日已用配额\n */\n public int getTodayUsage(Long tenantId) {\n if (tenantId == null) return 0;\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.get(key);\n return count == null ? 0 : count.get();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n IntelligenceInferenceResult result = invoke(\"ai-advisor\", systemPrompt, userMessage);\n if (!result.isSuccess()) {\n log.warn(\"[AiAdvisor] AI 调用失败 provider={} error={}\", result.getProvider(), result.getErrorMessage());\n return null;\n }\n return result.getContent();\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * <p>结果按 contextSummary 内容缓存 5 分钟,相同摘要(同一租户当日数据未变化)\n * 直接返回缓存值,避免重复调用 DeepSeek API(每次约 2~3 秒延迟)。\n * Redis 不可用时自动降级为直接调用 AI(见 RedisConfig.errorHandler)。</p>\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n @Cacheable(value = \"daily-brief\", key = \"T(com.fashion.supplychain.common.UserContext).tenantId() + ':' + T(java.time.LocalDate).now()\")\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n IntelligenceInferenceResult result = invoke(\"daily-brief\", systemPrompt, contextSummary);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n IntelligenceInferenceResult result = invoke(\"nl-intent\", systemPrompt, question);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n private IntelligenceInferenceResult invoke(String scene, String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n IntelligenceInferenceResult result = new IntelligenceInferenceResult();\n result.setSuccess(false);\n result.setProvider(\"none\");\n result.setErrorMessage(\"ai-disabled\");\n return result;\n }\n return intelligenceInferenceOrchestrator.chat(scene, systemPrompt, userMessage);\n }\n}\n"}, "fc10d435e3d8429461ad7e59b9cdd2179a093756": {"t": 1785749953.3053546, "n": 5838, "h": "0c462b9a466a0964", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.IntelligenceInferenceOrchestrator;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Service;\nimport org.springframework.context.annotation.Lazy;\n\nimport java.time.LocalDate;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * AI 顾问服务。\n *\n * <p>职责:保留配额与启用判断,对外提供 AI 顾问语义能力。\n * 真实推理由独立的 {@link IntelligenceInferenceOrchestrator} 执行。</p>\n *\n * <p>默认直连 DeepSeek,也支持通过 LiteLLM 网关接入,配置方式(环境变量):\n * <pre>\n * DEEPSEEK_API_KEY = sk-xxxxxxxxxxxxxxxx\n * AI_LITELLM_ENABLED = true/false\n * AI_LITELLM_BASE_URL = http://localhost:4000\n * AI_LITELLM_API_KEY = sk-virtual-key\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n */\n@Service\n@Lazy\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n /** 每个租户每天最多允许调用 DeepSeek 的次数(0 = 不限制) */\n @Value(\"${ai.deepseek.daily-quota-per-tenant:50}\")\n private int dailyQuotaPerTenant;\n\n @Autowired\n private IntelligenceInferenceOrchestrator intelligenceInferenceOrchestrator;\n\n /**\n * 每租户每日调用计数器\n * key = \"tenantId_yyyyMMdd\",value = 当日已调用次数\n * 不同日期的 key 自动过期(不再被读写),内存占用可忽略不计\n */\n private final ConcurrentHashMap<String, AtomicInteger> dailyCounters = new ConcurrentHashMap<>();\n\n @Scheduled(fixedRate = 3600000)\n public void cleanupStaleCounters() {\n String todaySuffix = \"_\" + LocalDate.now();\n dailyCounters.keySet().removeIf(key -> !key.endsWith(todaySuffix));\n }\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return intelligenceInferenceOrchestrator.isAnyModelEnabled();\n }\n\n /**\n * 检查当前租户今日配额是否已用完\n *\n * @param tenantId 租户ID(null 视为系统内部调用,不限制)\n * @return true = 配额充足可以调用;false = 今日配额已用完\n */\n public boolean checkAndConsumeQuota(Long tenantId) {\n if (dailyQuotaPerTenant <= 0 || tenantId == null) return true; // 0=不限\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.computeIfAbsent(key, k -> new AtomicInteger(0));\n int used = count.incrementAndGet();\n if (used > dailyQuotaPerTenant) {\n count.decrementAndGet(); // 回滚,不消耗\n log.warn(\"[AiAdvisor] 租户 {} 今日AI调用已达配额上限 {}\", tenantId, dailyQuotaPerTenant);\n return false;\n }\n log.debug(\"[AiAdvisor] 租户 {} 今日AI调用 {}/{}\", tenantId, used, dailyQuotaPerTenant);\n return true;\n }\n\n /**\n * 查询当前租户今日已用配额\n */\n public int getTodayUsage(Long tenantId) {\n if (tenantId == null) return 0;\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.get(key);\n return count == null ? 0 : count.get();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n IntelligenceInferenceResult result = invoke(\"ai-advisor\", systemPrompt, userMessage);\n if (!result.isSuccess()) {\n log.warn(\"[AiAdvisor] AI 调用失败 provider={} error={}\", result.getProvider(), result.getErrorMessage());\n return null;\n }\n return result.getContent();\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * <p>结果按 contextSummary 内容缓存 5 分钟,相同摘要(同一租户当日数据未变化)\n * 直接返回缓存值,避免重复调用 DeepSeek API(每次约 2~3 秒延迟)。\n * Redis 不可用时自动降级为直接调用 AI(见 RedisConfig.errorHandler)。</p>\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n @Cacheable(value = \"daily-brief\", key = \"T(com.fashion.supplychain.common.UserContext).tenantId() + ':' + T(java.time.LocalDate).now()\")\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n IntelligenceInferenceResult result = invoke(\"daily-brief\", systemPrompt, contextSummary);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n IntelligenceInferenceResult result = invoke(\"nl-intent\", systemPrompt, question);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n private IntelligenceInferenceResult invoke(String scene, String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n IntelligenceInferenceResult result = new IntelligenceInferenceResult();\n result.setSuccess(false);\n result.setProvider(\"none\");\n result.setErrorMessage(\"ai-disabled\");\n return result;\n }\n return intelligenceInferenceOrchestrator.chat(scene, systemPrompt, userMessage);\n }\n}\n"}, "cc55c6b31d802f926aac252da09310cf26df12ee": {"t": 1785749954.580515, "n": 5780, "h": "d524bdf2a0c2d6de", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.IntelligenceInferenceOrchestrator;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Service;\n\nimport java.time.LocalDate;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * AI 顾问服务。\n *\n * <p>职责:保留配额与启用判断,对外提供 AI 顾问语义能力。\n * 真实推理由独立的 {@link IntelligenceInferenceOrchestrator} 执行。</p>\n *\n * <p>默认直连 DeepSeek,也支持通过 LiteLLM 网关接入,配置方式(环境变量):\n * <pre>\n * DEEPSEEK_API_KEY = sk-xxxxxxxxxxxxxxxx\n * AI_LITELLM_ENABLED = true/false\n * AI_LITELLM_BASE_URL = http://localhost:4000\n * AI_LITELLM_API_KEY = sk-virtual-key\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n */\n@Service\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n /** 每个租户每天最多允许调用 DeepSeek 的次数(0 = 不限制) */\n @Value(\"${ai.deepseek.daily-quota-per-tenant:50}\")\n private int dailyQuotaPerTenant;\n\n @Autowired\n private IntelligenceInferenceOrchestrator intelligenceInferenceOrchestrator;\n\n /**\n * 每租户每日调用计数器\n * key = \"tenantId_yyyyMMdd\",value = 当日已调用次数\n * 不同日期的 key 自动过期(不再被读写),内存占用可忽略不计\n */\n private final ConcurrentHashMap<String, AtomicInteger> dailyCounters = new ConcurrentHashMap<>();\n\n @Scheduled(fixedRate = 3600000)\n public void cleanupStaleCounters() {\n String todaySuffix = \"_\" + LocalDate.now();\n dailyCounters.keySet().removeIf(key -> !key.endsWith(todaySuffix));\n }\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return intelligenceInferenceOrchestrator.isAnyModelEnabled();\n }\n\n /**\n * 检查当前租户今日配额是否已用完\n *\n * @param tenantId 租户ID(null 视为系统内部调用,不限制)\n * @return true = 配额充足可以调用;false = 今日配额已用完\n */\n public boolean checkAndConsumeQuota(Long tenantId) {\n if (dailyQuotaPerTenant <= 0 || tenantId == null) return true; // 0=不限\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.computeIfAbsent(key, k -> new AtomicInteger(0));\n int used = count.incrementAndGet();\n if (used > dailyQuotaPerTenant) {\n count.decrementAndGet(); // 回滚,不消耗\n log.warn(\"[AiAdvisor] 租户 {} 今日AI调用已达配额上限 {}\", tenantId, dailyQuotaPerTenant);\n return false;\n }\n log.debug(\"[AiAdvisor] 租户 {} 今日AI调用 {}/{}\", tenantId, used, dailyQuotaPerTenant);\n return true;\n }\n\n /**\n * 查询当前租户今日已用配额\n */\n public int getTodayUsage(Long tenantId) {\n if (tenantId == null) return 0;\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.get(key);\n return count == null ? 0 : count.get();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n IntelligenceInferenceResult result = invoke(\"ai-advisor\", systemPrompt, userMessage);\n if (!result.isSuccess()) {\n log.warn(\"[AiAdvisor] AI 调用失败 provider={} error={}\", result.getProvider(), result.getErrorMessage());\n return null;\n }\n return result.getContent();\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * <p>结果按 contextSummary 内容缓存 5 分钟,相同摘要(同一租户当日数据未变化)\n * 直接返回缓存值,避免重复调用 DeepSeek API(每次约 2~3 秒延迟)。\n * Redis 不可用时自动降级为直接调用 AI(见 RedisConfig.errorHandler)。</p>\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n @Cacheable(value = \"daily-brief\", key = \"T(com.fashion.supplychain.common.UserContext).tenantId() + ':' + T(java.time.LocalDate).now()\")\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n IntelligenceInferenceResult result = invoke(\"daily-brief\", systemPrompt, contextSummary);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n IntelligenceInferenceResult result = invoke(\"nl-intent\", systemPrompt, question);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n private IntelligenceInferenceResult invoke(String scene, String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n IntelligenceInferenceResult result = new IntelligenceInferenceResult();\n result.setSuccess(false);\n result.setProvider(\"none\");\n result.setErrorMessage(\"ai-disabled\");\n return result;\n }\n return intelligenceInferenceOrchestrator.chat(scene, systemPrompt, userMessage);\n }\n}\n"}, "5f3b3d1c5d9e1263d4af0f790474284a920c8a8f": {"t": 1785749957.7036502, "n": 5495, "h": "60fab315a6e32af6", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.IntelligenceInferenceOrchestrator;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport java.time.LocalDate;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * AI 顾问服务。\n *\n * <p>职责:保留配额与启用判断,对外提供 AI 顾问语义能力。\n * 真实推理由独立的 {@link IntelligenceInferenceOrchestrator} 执行。</p>\n *\n * <p>默认直连 DeepSeek,也支持通过 LiteLLM 网关接入,配置方式(环境变量):\n * <pre>\n * DEEPSEEK_API_KEY = sk-xxxxxxxxxxxxxxxx\n * AI_LITELLM_ENABLED = true/false\n * AI_LITELLM_BASE_URL = http://localhost:4000\n * AI_LITELLM_API_KEY = sk-virtual-key\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n */\n@Service\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n /** 每个租户每天最多允许调用 DeepSeek 的次数(0 = 不限制) */\n @Value(\"${ai.deepseek.daily-quota-per-tenant:50}\")\n private int dailyQuotaPerTenant;\n\n @Autowired\n private IntelligenceInferenceOrchestrator intelligenceInferenceOrchestrator;\n\n /**\n * 每租户每日调用计数器\n * key = \"tenantId_yyyyMMdd\",value = 当日已调用次数\n * 不同日期的 key 自动过期(不再被读写),内存占用可忽略不计\n */\n private final ConcurrentHashMap<String, AtomicInteger> dailyCounters = new ConcurrentHashMap<>();\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return intelligenceInferenceOrchestrator.isAnyModelEnabled();\n }\n\n /**\n * 检查当前租户今日配额是否已用完\n *\n * @param tenantId 租户ID(null 视为系统内部调用,不限制)\n * @return true = 配额充足可以调用;false = 今日配额已用完\n */\n public boolean checkAndConsumeQuota(Long tenantId) {\n if (dailyQuotaPerTenant <= 0 || tenantId == null) return true; // 0=不限\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.computeIfAbsent(key, k -> new AtomicInteger(0));\n int used = count.incrementAndGet();\n if (used > dailyQuotaPerTenant) {\n count.decrementAndGet(); // 回滚,不消耗\n log.warn(\"[AiAdvisor] 租户 {} 今日AI调用已达配额上限 {}\", tenantId, dailyQuotaPerTenant);\n return false;\n }\n log.debug(\"[AiAdvisor] 租户 {} 今日AI调用 {}/{}\", tenantId, used, dailyQuotaPerTenant);\n return true;\n }\n\n /**\n * 查询当前租户今日已用配额\n */\n public int getTodayUsage(Long tenantId) {\n if (tenantId == null) return 0;\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.get(key);\n return count == null ? 0 : count.get();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n IntelligenceInferenceResult result = invoke(\"ai-advisor\", systemPrompt, userMessage);\n if (!result.isSuccess()) {\n log.warn(\"[AiAdvisor] AI 调用失败 provider={} error={}\", result.getProvider(), result.getErrorMessage());\n return null;\n }\n return result.getContent();\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * <p>结果按 contextSummary 内容缓存 5 分钟,相同摘要(同一租户当日数据未变化)\n * 直接返回缓存值,避免重复调用 DeepSeek API(每次约 2~3 秒延迟)。\n * Redis 不可用时自动降级为直接调用 AI(见 RedisConfig.errorHandler)。</p>\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n @Cacheable(value = \"daily-brief\", key = \"T(com.fashion.supplychain.common.UserContext).tenantId() + ':' + #contextSummary\")\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n IntelligenceInferenceResult result = invoke(\"daily-brief\", systemPrompt, contextSummary);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n IntelligenceInferenceResult result = invoke(\"nl-intent\", systemPrompt, question);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n private IntelligenceInferenceResult invoke(String scene, String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n IntelligenceInferenceResult result = new IntelligenceInferenceResult();\n result.setSuccess(false);\n result.setProvider(\"none\");\n result.setErrorMessage(\"ai-disabled\");\n return result;\n }\n return intelligenceInferenceOrchestrator.chat(scene, systemPrompt, userMessage);\n }\n}\n"}, "64c4685b5bf2577e8ed6f8aff55fa2b7fa6cf319": {"t": 1785749958.7763484, "n": 5543, "h": "15755135f48490ef", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.IntelligenceInferenceOrchestrator;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport java.time.LocalDate;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * AI 顾问服务。\n *\n * <p>职责:保留配额与启用判断,对外提供 AI 顾问语义能力。\n * 真实推理由独立的 {@link IntelligenceInferenceOrchestrator} 执行。</p>\n *\n * <p>默认直连 DeepSeek,也支持通过 LiteLLM 网关接入,配置方式(环境变量):\n * <pre>\n * DEEPSEEK_API_KEY = sk-xxxxxxxxxxxxxxxx\n * AI_LITELLM_ENABLED = true/false\n * AI_LITELLM_BASE_URL = http://localhost:4000\n * AI_LITELLM_API_KEY = sk-virtual-key\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n */\n@Service\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n /** 每个租户每天最多允许调用 DeepSeek 的次数(0 = 不限制) */\n @Value(\"${ai.deepseek.daily-quota-per-tenant:50}\")\n private int dailyQuotaPerTenant;\n\n @Autowired\n private IntelligenceInferenceOrchestrator intelligenceInferenceOrchestrator;\n\n /**\n * 每租户每日调用计数器\n * key = \"tenantId_yyyyMMdd\",value = 当日已调用次数\n * 不同日期的 key 自动过期(不再被读写),内存占用可忽略不计\n */\n private final ConcurrentHashMap<String, AtomicInteger> dailyCounters = new ConcurrentHashMap<>();\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return intelligenceInferenceOrchestrator.isAnyModelEnabled();\n }\n\n /**\n * 检查当前租户今日配额是否已用完\n *\n * @param tenantId 租户ID(null 视为系统内部调用,不限制)\n * @return true = 配额充足可以调用;false = 今日配额已用完\n */\n public boolean checkAndConsumeQuota(Long tenantId) {\n if (dailyQuotaPerTenant <= 0 || tenantId == null) return true; // 0=不限\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.computeIfAbsent(key, k -> new AtomicInteger(0));\n int used = count.incrementAndGet();\n if (used > dailyQuotaPerTenant) {\n count.decrementAndGet(); // 回滚,不消耗\n log.warn(\"[AiAdvisor] 租户 {} 今日AI调用已达配额上限 {}\", tenantId, dailyQuotaPerTenant);\n return false;\n }\n log.debug(\"[AiAdvisor] 租户 {} 今日AI调用 {}/{}\", tenantId, used, dailyQuotaPerTenant);\n return true;\n }\n\n /**\n * 查询当前租户今日已用配额\n */\n public int getTodayUsage(Long tenantId) {\n if (tenantId == null) return 0;\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.get(key);\n return count == null ? 0 : count.get();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n IntelligenceInferenceResult result = invoke(\"ai-advisor\", systemPrompt, userMessage);\n if (!result.isSuccess()) {\n log.warn(\"[AiAdvisor] AI 调用失败 provider={} error={}\", result.getProvider(), result.getErrorMessage());\n return null;\n }\n return result.getContent();\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * <p>结果按 contextSummary 内容缓存 5 分钟,相同摘要(同一租户当日数据未变化)\n * 直接返回缓存值,避免重复调用 DeepSeek API(每次约 2~3 秒延迟)。\n * Redis 不可用时自动降级为直接调用 AI(见 RedisConfig.errorHandler)。</p>\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n @Cacheable(value = \"daily-brief\", key = \"T(com.fashion.supplychain.common.UserContext).tenantId() + ':' + #contextSummary\")\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n IntelligenceInferenceResult result = invoke(\"daily-brief\", systemPrompt, contextSummary);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n IntelligenceInferenceResult result = invoke(\"nl-intent\", systemPrompt, question);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n private IntelligenceInferenceResult invoke(String scene, String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n IntelligenceInferenceResult result = new IntelligenceInferenceResult();\n result.setSuccess(false);\n result.setProvider(\"none\");\n result.setErrorMessage(\"ai-disabled\");\n return result;\n }\n return intelligenceInferenceOrchestrator.chat(scene, systemPrompt, userMessage);\n }\n}\n"}, "34014ba5d2031264de5c960eaad707590ce3b729": {"t": 1785749959.8803394, "n": 5478, "h": "3cfa15a117d66e9f", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.IntelligenceInferenceOrchestrator;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport java.time.LocalDate;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * AI 顾问服务。\n *\n * <p>职责:保留配额与启用判断,对外提供 AI 顾问语义能力。\n * 真实推理由独立的 {@link IntelligenceInferenceOrchestrator} 执行。</p>\n *\n * <p>默认直连 DeepSeek,也支持通过 LiteLLM 网关接入,配置方式(环境变量):\n * <pre>\n * DEEPSEEK_API_KEY = sk-xxxxxxxxxxxxxxxx\n * AI_LITELLM_ENABLED = true/false\n * AI_LITELLM_BASE_URL = http://localhost:4000\n * AI_LITELLM_API_KEY = sk-virtual-key\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n */\n@Service\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n /** 每个租户每天最多允许调用 DeepSeek 的次数(0 = 不限制) */\n @Value(\"${ai.deepseek.daily-quota-per-tenant:50}\")\n private int dailyQuotaPerTenant;\n\n @Autowired\n private IntelligenceInferenceOrchestrator intelligenceInferenceOrchestrator;\n\n /**\n * 每租户每日调用计数器\n * key = \"tenantId_yyyyMMdd\",value = 当日已调用次数\n * 不同日期的 key 自动过期(不再被读写),内存占用可忽略不计\n */\n private final ConcurrentHashMap<String, AtomicInteger> dailyCounters = new ConcurrentHashMap<>();\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return intelligenceInferenceOrchestrator.isAnyModelEnabled();\n }\n\n /**\n * 检查当前租户今日配额是否已用完\n *\n * @param tenantId 租户ID(null 视为系统内部调用,不限制)\n * @return true = 配额充足可以调用;false = 今日配额已用完\n */\n public boolean checkAndConsumeQuota(Long tenantId) {\n if (dailyQuotaPerTenant <= 0 || tenantId == null) return true; // 0=不限\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.computeIfAbsent(key, k -> new AtomicInteger(0));\n int used = count.incrementAndGet();\n if (used > dailyQuotaPerTenant) {\n count.decrementAndGet(); // 回滚,不消耗\n log.warn(\"[AiAdvisor] 租户 {} 今日AI调用已达配额上限 {}\", tenantId, dailyQuotaPerTenant);\n return false;\n }\n log.debug(\"[AiAdvisor] 租户 {} 今日AI调用 {}/{}\", tenantId, used, dailyQuotaPerTenant);\n return true;\n }\n\n /**\n * 查询当前租户今日已用配额\n */\n public int getTodayUsage(Long tenantId) {\n if (tenantId == null) return 0;\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.get(key);\n return count == null ? 0 : count.get();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n IntelligenceInferenceResult result = invoke(\"ai-advisor\", systemPrompt, userMessage);\n if (!result.isSuccess()) {\n log.warn(\"[AiAdvisor] AI 调用失败 provider={} error={}\", result.getProvider(), result.getErrorMessage());\n return null;\n }\n return result.getContent();\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * <p>结果按 contextSummary 内容缓存 5 分钟,相同摘要(同一租户当日数据未变化)\n * 直接返回缓存值,避免重复调用 DeepSeek API(每次约 2~3 秒延迟)。\n * Redis 不可用时自动降级为直接调用 AI(见 RedisConfig.errorHandler)。</p>\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n @Cacheable(value = \"daily-brief\", key = \"#contextSummary\")\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n IntelligenceInferenceResult result = invoke(\"daily-brief\", systemPrompt, contextSummary);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n IntelligenceInferenceResult result = invoke(\"nl-intent\", systemPrompt, question);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n private IntelligenceInferenceResult invoke(String scene, String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n IntelligenceInferenceResult result = new IntelligenceInferenceResult();\n result.setSuccess(false);\n result.setProvider(\"none\");\n result.setErrorMessage(\"ai-disabled\");\n return result;\n }\n return intelligenceInferenceOrchestrator.chat(scene, systemPrompt, userMessage);\n }\n}\n"}, "ee39636df0ebdc4a1f9923cc34d0684344a4bf4b": {"t": 1785749960.9941413, "n": 5184, "h": "f1a7f3b07fb8ce2d", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fashion.supplychain.intelligence.dto.IntelligenceInferenceResult;\nimport com.fashion.supplychain.intelligence.orchestration.IntelligenceInferenceOrchestrator;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.time.LocalDate;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * AI 顾问服务。\n *\n * <p>职责:保留配额与启用判断,对外提供 AI 顾问语义能力。\n * 真实推理由独立的 {@link IntelligenceInferenceOrchestrator} 执行。</p>\n *\n * <p>默认直连 DeepSeek,也支持通过 LiteLLM 网关接入,配置方式(环境变量):\n * <pre>\n * DEEPSEEK_API_KEY = sk-xxxxxxxxxxxxxxxx\n * AI_LITELLM_ENABLED = true/false\n * AI_LITELLM_BASE_URL = http://localhost:4000\n * AI_LITELLM_API_KEY = sk-virtual-key\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n */\n@Service\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n /** 每个租户每天最多允许调用 DeepSeek 的次数(0 = 不限制) */\n @Value(\"${ai.deepseek.daily-quota-per-tenant:50}\")\n private int dailyQuotaPerTenant;\n\n @Autowired\n private IntelligenceInferenceOrchestrator intelligenceInferenceOrchestrator;\n\n /**\n * 每租户每日调用计数器\n * key = \"tenantId_yyyyMMdd\",value = 当日已调用次数\n * 不同日期的 key 自动过期(不再被读写),内存占用可忽略不计\n */\n private final ConcurrentHashMap<String, AtomicInteger> dailyCounters = new ConcurrentHashMap<>();\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return intelligenceInferenceOrchestrator.isAnyModelEnabled();\n }\n\n /**\n * 检查当前租户今日配额是否已用完\n *\n * @param tenantId 租户ID(null 视为系统内部调用,不限制)\n * @return true = 配额充足可以调用;false = 今日配额已用完\n */\n public boolean checkAndConsumeQuota(Long tenantId) {\n if (dailyQuotaPerTenant <= 0 || tenantId == null) return true; // 0=不限\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.computeIfAbsent(key, k -> new AtomicInteger(0));\n int used = count.incrementAndGet();\n if (used > dailyQuotaPerTenant) {\n count.decrementAndGet(); // 回滚,不消耗\n log.warn(\"[AiAdvisor] 租户 {} 今日AI调用已达配额上限 {}\", tenantId, dailyQuotaPerTenant);\n return false;\n }\n log.debug(\"[AiAdvisor] 租户 {} 今日AI调用 {}/{}\", tenantId, used, dailyQuotaPerTenant);\n return true;\n }\n\n /**\n * 查询当前租户今日已用配额\n */\n public int getTodayUsage(Long tenantId) {\n if (tenantId == null) return 0;\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.get(key);\n return count == null ? 0 : count.get();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n IntelligenceInferenceResult result = invoke(\"ai-advisor\", systemPrompt, userMessage);\n if (!result.isSuccess()) {\n log.warn(\"[AiAdvisor] AI 调用失败 provider={} error={}\", result.getProvider(), result.getErrorMessage());\n return null;\n }\n return result.getContent();\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n IntelligenceInferenceResult result = invoke(\"daily-brief\", systemPrompt, contextSummary);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n IntelligenceInferenceResult result = invoke(\"nl-intent\", systemPrompt, question);\n return result.isSuccess() ? result.getContent() : null;\n }\n\n private IntelligenceInferenceResult invoke(String scene, String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n IntelligenceInferenceResult result = new IntelligenceInferenceResult();\n result.setSuccess(false);\n result.setProvider(\"none\");\n result.setErrorMessage(\"ai-disabled\");\n return result;\n }\n return intelligenceInferenceOrchestrator.chat(scene, systemPrompt, userMessage);\n }\n}\n"}, "6f4ed6d1a104aecf5e94fb4632c129e2a83d710e": {"t": 1785749963.079358, "n": 6578, "h": "164114db2d13a06e", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.time.LocalDate;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * AI 顾问服务 — 统一的 AI API 接入层\n *\n * <p>默认接 DeepSeek,配置方式(环境变量):\n * <pre>\n * AI_API_KEY = sk-xxxxxxxxxxxxxxxx (必填,有此值才会真实调用 AI)\n * AI_API_URL = https://api.deepseek.com/v1/chat/completions (可选,默认 DeepSeek)\n * AI_MODEL = deepseek-chat (可选,默认 deepseek-chat)\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n *\n * <p>切换 OpenAI / 其他兼容 API 只需改 AI_API_URL 和 AI_MODEL。\n */\n@Service\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n @Value(\"${ai.deepseek.api-url:https://api.deepseek.com/v1/chat/completions}\")\n private String apiUrl;\n\n @Value(\"${ai.deepseek.model:deepseek-chat}\")\n private String model;\n\n /** 每个租户每天最多允许调用 DeepSeek 的次数(0 = 不限制) */\n @Value(\"${ai.deepseek.daily-quota-per-tenant:50}\")\n private int dailyQuotaPerTenant;\n\n private static final int TIMEOUT_SECONDS = 30;\n private static final ObjectMapper MAPPER = new ObjectMapper();\n\n /**\n * 每租户每日调用计数器\n * key = \"tenantId_yyyyMMdd\",value = 当日已调用次数\n * 不同日期的 key 自动过期(不再被读写),内存占用可忽略不计\n */\n private final ConcurrentHashMap<String, AtomicInteger> dailyCounters = new ConcurrentHashMap<>();\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return apiKey != null && !apiKey.isBlank();\n }\n\n /**\n * 检查当前租户今日配额是否已用完\n *\n * @param tenantId 租户ID(null 视为系统内部调用,不限制)\n * @return true = 配额充足可以调用;false = 今日配额已用完\n */\n public boolean checkAndConsumeQuota(Long tenantId) {\n if (dailyQuotaPerTenant <= 0 || tenantId == null) return true; // 0=不限\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.computeIfAbsent(key, k -> new AtomicInteger(0));\n int used = count.incrementAndGet();\n if (used > dailyQuotaPerTenant) {\n count.decrementAndGet(); // 回滚,不消耗\n log.warn(\"[AiAdvisor] 租户 {} 今日AI调用已达配额上限 {}\", tenantId, dailyQuotaPerTenant);\n return false;\n }\n log.debug(\"[AiAdvisor] 租户 {} 今日AI调用 {}/{}\", tenantId, used, dailyQuotaPerTenant);\n return true;\n }\n\n /**\n * 查询当前租户今日已用配额\n */\n public int getTodayUsage(Long tenantId) {\n if (tenantId == null) return 0;\n String key = tenantId + \"_\" + LocalDate.now();\n AtomicInteger count = dailyCounters.get(key);\n return count == null ? 0 : count.get();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n log.debug(\"[AiAdvisor] AI_API_KEY 未配置,跳过 AI 调用\");\n return null;\n }\n\n try {\n String body = buildRequestBody(systemPrompt, userMessage);\n HttpClient client = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(body))\n .timeout(Duration.ofSeconds(TIMEOUT_SECONDS))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n\n if (response.statusCode() == 200) {\n return extractContent(response.body());\n } else {\n log.warn(\"[AiAdvisor] API 返回非200: status={}, body={}\", response.statusCode(),\n response.body().substring(0, Math.min(200, response.body().length())));\n return null;\n }\n } catch (Exception e) {\n log.warn(\"[AiAdvisor] AI 调用失败: {}\", e.getMessage());\n return null;\n }\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n return chat(systemPrompt, contextSummary);\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n return chat(systemPrompt, question);\n }\n\n // ────────────────── 私有方法 ──────────────────\n\n private String buildRequestBody(String systemPrompt, String userMessage) throws Exception {\n var root = MAPPER.createObjectNode();\n root.put(\"model\", model);\n root.put(\"temperature\", 0.3);\n root.put(\"max_tokens\", 512);\n var messages = root.putArray(\"messages\");\n messages.addObject().put(\"role\", \"system\").put(\"content\", systemPrompt);\n messages.addObject().put(\"role\", \"user\").put(\"content\", userMessage);\n return MAPPER.writeValueAsString(root);\n }\n\n private String extractContent(String responseBody) throws Exception {\n JsonNode root = MAPPER.readTree(responseBody);\n JsonNode choices = root.path(\"choices\");\n if (choices.isArray() && choices.size() > 0) {\n return choices.get(0).path(\"message\").path(\"content\").asText(null);\n }\n return null;\n }\n}\n"}, "fdd7d49b70ea84d9b24ec29bc480b05a726ff117": {"t": 1785749969.0282362, "n": 4998, "h": "61094422963d0eb0", "c": "package com.fashion.supplychain.intelligence.service;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\n\n/**\n * AI 顾问服务 — 统一的 AI API 接入层\n *\n * <p>默认接 DeepSeek,配置方式(环境变量):\n * <pre>\n * AI_API_KEY = sk-xxxxxxxxxxxxxxxx (必填,有此值才会真实调用 AI)\n * AI_API_URL = https://api.deepseek.com/v1/chat/completions (可选,默认 DeepSeek)\n * AI_MODEL = deepseek-chat (可选,默认 deepseek-chat)\n * </pre>\n *\n * <p>若未配置 AI_API_KEY,所有调用会立即返回 null,系统继续走本地规则引擎,\n * 不影响任何现有功能。\n *\n * <p>切换 OpenAI / 其他兼容 API 只需改 AI_API_URL 和 AI_MODEL。\n */\n@Service\n@Slf4j\npublic class AiAdvisorService {\n\n @Value(\"${ai.deepseek.api-key:}\")\n private String apiKey;\n\n @Value(\"${ai.deepseek.api-url:https://api.deepseek.com/v1/chat/completions}\")\n private String apiUrl;\n\n @Value(\"${ai.deepseek.model:deepseek-chat}\")\n private String model;\n\n private static final int TIMEOUT_SECONDS = 30;\n private static final ObjectMapper MAPPER = new ObjectMapper();\n\n /**\n * 是否已启用 AI(有 API Key)\n */\n public boolean isEnabled() {\n return apiKey != null && !apiKey.isBlank();\n }\n\n /**\n * 发送单轮对话请求\n *\n * @param systemPrompt 系统角色描述(如:你是服装供应链分析师)\n * @param userMessage 用户消息\n * @return AI 回复文本,未配置 Key 或调用失败时返回 null\n */\n public String chat(String systemPrompt, String userMessage) {\n if (!isEnabled()) {\n log.debug(\"[AiAdvisor] AI_API_KEY 未配置,跳过 AI 调用\");\n return null;\n }\n\n try {\n String body = buildRequestBody(systemPrompt, userMessage);\n HttpClient client = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(body))\n .timeout(Duration.ofSeconds(TIMEOUT_SECONDS))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n\n if (response.statusCode() == 200) {\n return extractContent(response.body());\n } else {\n log.warn(\"[AiAdvisor] API 返回非200: status={}, body={}\", response.statusCode(),\n response.body().substring(0, Math.min(200, response.body().length())));\n return null;\n }\n } catch (Exception e) {\n log.warn(\"[AiAdvisor] AI 调用失败: {}\", e.getMessage());\n return null;\n }\n }\n\n /**\n * 智能建议:给定业务数据摘要,返回简短建议文案(用于运营日报)\n *\n * @param contextSummary 业务摘要(如:今日逾期3单,高风险2单,停滞1单)\n * @return 1~3句建议文案,未启用时返回 null\n */\n public String getDailyAdvice(String contextSummary) {\n String systemPrompt = \"你是一名服装供应链管理顾问,根据工厂今日生产数据,\" +\n \"用简洁中文给出1~3条可执行的管理建议。每条建议一行,不超过30字。不要废话。\";\n return chat(systemPrompt, contextSummary);\n }\n\n /**\n * 自然语言转结构化查询意图分析\n * 当 NlQueryOrchestrator 本地规则无法匹配时,调用此方法让 AI 理解意图\n *\n * @param question 用户问题\n * @return JSON 字符串,格式:{\"intent\":\"xxx\",\"params\":{\"key\":\"value\"}}\n * 失败返回 null\n */\n public String parseNlIntent(String question) {\n String systemPrompt = \"你是服装供应链系统的 NLP 解析器。\" +\n \"将用户问题解析为 JSON:{\\\"intent\\\":\\\"意图\\\",\\\"params\\\":{\\\"参数\\\":\\\"值\\\"}}。\" +\n \"可能的 intent: query_order_status, query_material_stock, query_factory_capacity, \" +\n \"query_finance_settlement, query_worker_efficiency, query_delivery_risk。\" +\n \"只输出 JSON,不要解释。\";\n return chat(systemPrompt, question);\n }\n\n // ────────────────── 私有方法 ──────────────────\n\n private String buildRequestBody(String systemPrompt, String userMessage) throws Exception {\n var root = MAPPER.createObjectNode();\n root.put(\"model\", model);\n root.put(\"temperature\", 0.3);\n root.put(\"max_tokens\", 512);\n var messages = root.putArray(\"messages\");\n messages.addObject().put(\"role\", \"system\").put(\"content\", systemPrompt);\n messages.addObject().put(\"role\", \"user\").put(\"content\", userMessage);\n return MAPPER.writeValueAsString(root);\n }\n\n private String extractContent(String responseBody) throws Exception {\n JsonNode root = MAPPER.readTree(responseBody);\n JsonNode choices = root.path(\"choices\");\n if (choices.isArray() && choices.size() > 0) {\n return choices.get(0).path(\"message\").path(\"content\").asText(null);\n }\n return null;\n }\n}\n"}}}}