交付"别人能自己把系统跑起来做演示"所需的四件东西: - tools/seed_demo_data.py 演示数据一键准备:10 步按依赖排序 (此前散在 10 个脚本里,没人知道该跑哪些、按什么顺序跑,且知识库那步 根本没有脚本、靠手工调接口) - tools/seed_knowledge_demo.py 知识库演示素材灌入,幂等(先删同 source_file 再灌) - start.ps1 启动 API + Worker,含依赖检查与**自动刷新行情** - docs/44-演示流程.md 8 个主线场景的照读流程 + 排障表 + 账号/命令速查 两条硬约束同时写进了脚本和文档: 1. **Worker 必须常驻**:没有它客服对话一直停在 queued(前端只显示"超时")、 新知识不进 Milvus 且**没有任何报错**。 2. **行情有效期仅 15 分钟**(`trade_service.MAX_QUOTE_AGE`):超时后所有委托 直接 503「行情已过期」,而系统**没有自动刷新机制**。故 start.ps1 启动时刷一次, 文档另给"演示中途 503 时补刷、无需重启服务"的处置方法(已实测)。 实测证据: - start.ps1 在 8099 完整启动,`/docs` 与 `/portal/` 均 HTTP 200(测试进程已清理) - `tools/e2e_smoke_test.py` → 40/40 通过 - `tools/seed_demo_data.py --dry-run` → 10 步全部正常列出 - 文档中的下单命令实跑:510300 成交价 4.579、金额 457.90、手续费 0.05
168 lines
7.8 KiB
PowerShell
168 lines
7.8 KiB
PowerShell
<#
|
||
.SYNOPSIS
|
||
启动金融 Agent 平台:依赖检查 → API → Agent Worker。
|
||
|
||
.NOTES
|
||
⚠️ 本文件必须保存为 **UTF-8 with BOM**。
|
||
Windows PowerShell 5.1 在缺少 BOM 时按系统 ANSI 解析脚本(简体中文下是 GBK),
|
||
中文注释与输出会变成乱码并直接抛语法错误(实测报 `Unexpected token '[璀﹀憡]'`)。
|
||
用编辑器改完本文件后,务必另存为「UTF-8 带 BOM」。
|
||
|
||
.DESCRIPTION
|
||
平台需要**两个进程**才能完整工作,本脚本把它们一起起在独立窗口里:
|
||
|
||
· API —— 所有 HTTP 接口与前端页面(/portal/)
|
||
· Worker —— 消费队列:Agent 对话、知识向量同步、记忆抽取、风控扫描
|
||
|
||
**没有 Worker 的后果**(这也是最容易踩的坑):
|
||
· 客服对话会一直停在 queued,前端显示"客服繁忙/超时";
|
||
· 新灌的知识写不进 Milvus,客服照旧答不上,且**没有任何报错**。
|
||
|
||
.PARAMETER Port
|
||
API 监听端口,默认 8000。
|
||
|
||
.PARAMETER SkipChecks
|
||
跳过依赖端口检查(MySQL / Redis / Milvus)。
|
||
|
||
.PARAMETER ApiOnly
|
||
只起 API,不起 Worker(仅在明确不需要 Agent 能力时使用)。
|
||
|
||
.PARAMETER SkipPriceSync
|
||
跳过启动时的行情刷新。仅在外部数据源不可用、或不想联网时使用。
|
||
|
||
.EXAMPLE
|
||
powershell -ExecutionPolicy Bypass -File start.ps1
|
||
powershell -ExecutionPolicy Bypass -File start.ps1 -Port 8100
|
||
#>
|
||
param(
|
||
[int]$Port = 8000,
|
||
[switch]$SkipChecks,
|
||
[switch]$ApiOnly,
|
||
[switch]$SkipPriceSync
|
||
)
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
$Root = $PSScriptRoot
|
||
Set-Location $Root
|
||
|
||
Write-Host "=== 金融 Agent 平台 · 启动 ===" -ForegroundColor Cyan
|
||
Write-Host "工作目录:$Root"
|
||
|
||
# ---------------------------------------------------------------- 解释器
|
||
# 逐个尝试:项目虚拟环境 → 常见 conda 环境 → PATH 上的 python。
|
||
# 不写死任何一个,因为各人机器的环境不同(.venv 被 gitignore,不进仓库)。
|
||
$candidates = @(
|
||
(Join-Path $Root ".venv\Scripts\python.exe"),
|
||
"D:\conda\envs\jr_py313\python.exe",
|
||
"python"
|
||
)
|
||
$Python = $null
|
||
foreach ($candidate in $candidates) {
|
||
try {
|
||
if ($candidate -eq "python") {
|
||
$version = & python --version 2>&1
|
||
} else {
|
||
if (-not (Test-Path $candidate)) { continue }
|
||
$version = & $candidate --version 2>&1
|
||
}
|
||
if ($LASTEXITCODE -eq 0) {
|
||
$Python = $candidate
|
||
Write-Host "解释器:$candidate($version)" -ForegroundColor Green
|
||
break
|
||
}
|
||
} catch { continue }
|
||
}
|
||
if (-not $Python) {
|
||
Write-Host "[失败] 找不到可用的 Python。请先建虚拟环境,或把解释器路径加进本脚本的候选列表。" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
|
||
# ---------------------------------------------------------------- 依赖检查
|
||
if (-not $SkipChecks) {
|
||
Write-Host "`n--- 依赖服务检查 ---" -ForegroundColor Cyan
|
||
$services = @(
|
||
@{ Name = "MySQL"; HostName = "127.0.0.1"; Port = 3306; Required = $true },
|
||
@{ Name = "Redis"; HostName = "127.0.0.1"; Port = 6379; Required = $false },
|
||
@{ Name = "Milvus"; HostName = "127.0.0.1"; Port = 19530; Required = $false }
|
||
)
|
||
foreach ($service in $services) {
|
||
$reachable = $false
|
||
try {
|
||
$reachable = (Test-NetConnection -ComputerName $service.HostName -Port $service.Port `
|
||
-InformationLevel Quiet -WarningAction SilentlyContinue)
|
||
} catch { $reachable = $false }
|
||
|
||
if ($reachable) {
|
||
Write-Host (" [OK] {0,-7} {1}:{2}" -f $service.Name, $service.HostName, $service.Port) -ForegroundColor Green
|
||
} elseif ($service.Required) {
|
||
Write-Host (" [缺失] {0,-7} {1}:{2} —— 平台起不来,请先启动它" -f $service.Name, $service.HostName, $service.Port) -ForegroundColor Red
|
||
exit 1
|
||
} else {
|
||
Write-Host (" [警告] {0,-7} {1}:{2} 不可达:相关知识检索会降级、Docker 未运行时常见" -f $service.Name, $service.HostName, $service.Port) -ForegroundColor Yellow
|
||
}
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- 刷新行情
|
||
# ⚠️ 为什么放在启动流程里:下单要求行情快照落在 **15 分钟**有效期内
|
||
# (`app/service/trade_service.py` 的 `MAX_QUOTE_AGE`),超时后**所有委托直接 503
|
||
# 「行情已过期」**,而系统**没有任何自动刷新机制** —— 演示时讲到一半下单就会失败。
|
||
# 所以默认在启动时刷一次:15 分钟窗口从此刻重新计时。
|
||
# 同步是按产品 upsert 的幂等操作;失败只警告不阻断(仍可手动重跑)。
|
||
if (-not $SkipPriceSync) {
|
||
Write-Host "`n--- 刷新行情(下单前置,有效期 15 分钟)---" -ForegroundColor Cyan
|
||
$syncOutput = & $Python "tools\sync_market_prices.py" 2>&1
|
||
if ($LASTEXITCODE -eq 0) {
|
||
Write-Host " [OK] 行情已刷新,15 分钟内可正常下单" -ForegroundColor Green
|
||
} else {
|
||
Write-Host " [警告] 行情刷新失败(退出码 $LASTEXITCODE):下单可能返回 503" -ForegroundColor Yellow
|
||
Write-Host " 可手动重跑:$Python tools\sync_market_prices.py" -ForegroundColor Yellow
|
||
$syncOutput | Select-Object -Last 8 | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- 启动进程
|
||
# 用独立窗口起,方便分别看两边的日志(Worker 的日志是排查问题的第一现场)。
|
||
function Start-PlatformWindow {
|
||
param([string]$Title, [string[]]$Arguments)
|
||
$shell = if (Get-Command pwsh -ErrorAction SilentlyContinue) { "pwsh" } else { "powershell" }
|
||
$command = "`$host.UI.RawUI.WindowTitle = '$Title'; & '$Python' " + ($Arguments -join " ")
|
||
Start-Process -FilePath $shell -ArgumentList "-NoExit", "-Command", $command | Out-Null
|
||
}
|
||
|
||
Write-Host "`n--- 启动进程 ---" -ForegroundColor Cyan
|
||
Start-PlatformWindow -Title "平台 API :$Port" -Arguments @(
|
||
"-m", "uvicorn", "app.main:app", "--host", "127.0.0.1", "--port", "$Port"
|
||
)
|
||
Write-Host " [已启动] API 窗口(uvicorn,端口 $Port)" -ForegroundColor Green
|
||
|
||
if (-not $ApiOnly) {
|
||
Start-PlatformWindow -Title "平台 Agent Worker" -Arguments @("-m", "app.worker")
|
||
Write-Host " [已启动] Worker 窗口(Agent 对话 / 知识向量 / 记忆抽取)" -ForegroundColor Green
|
||
} else {
|
||
Write-Host " [跳过] Worker(-ApiOnly):客服对话会一直 queued、新知识不会进 Milvus" -ForegroundColor Yellow
|
||
}
|
||
|
||
# ---------------------------------------------------------------- 访问入口
|
||
Start-Sleep -Seconds 3
|
||
Write-Host "`n=== 访问入口 ===" -ForegroundColor Cyan
|
||
Write-Host " 门户首页 http://127.0.0.1:$Port/portal/"
|
||
Write-Host " 访客页 http://127.0.0.1:$Port/portal/guest/home/"
|
||
Write-Host " 客户登录 http://127.0.0.1:$Port/portal/customer/login/ (cust_t / 123456)"
|
||
Write-Host " 员工登录 http://127.0.0.1:$Port/portal/employee-console/login/(见下)"
|
||
Write-Host " 接口文档 http://127.0.0.1:$Port/docs"
|
||
|
||
Write-Host "`n=== 演示账号 ===" -ForegroundColor Cyan
|
||
Write-Host " 客户 cust_t / 123456"
|
||
Write-Host " 风控专员 risk_t / 666666"
|
||
Write-Host " 管理员 admin_t / 88888888"
|
||
Write-Host " 投顾 advisor_t / abc12345"
|
||
Write-Host " 运营 offsite_t / offsite123"
|
||
|
||
Write-Host "`n提示:" -ForegroundColor Yellow
|
||
Write-Host " · 首次使用先准备数据:python tools/seed_demo_data.py"
|
||
Write-Host " · 行情有效期只有 15 分钟:演示中途下单若报 503,在新窗口重跑"
|
||
Write-Host " python tools/sync_market_prices.py (立即生效,无需重启服务)"
|
||
Write-Host " · 体检:python tools/e2e_smoke_test.py"
|
||
Write-Host " · 演示流程见 docs/44-演示流程.md"
|