feat: add AI research chatbot with confirmed business tools

This commit is contained in:
yuxuanhui
2026-09-07 23:02:55 +08:00
parent 3cd280d068
commit 79ab20b4ea
44 changed files with 4647 additions and 197 deletions
+123 -1
View File
@@ -89,6 +89,7 @@ asyncio.run(seed())
"PATCH",
{
"note": "persistent local note",
"version": 1,
"tags": ["docker-verified"],
"state": "candidate",
"favorite": True,
@@ -105,6 +106,78 @@ asyncio.run(seed())
assert detail["sharpe"] == 3 and detail["research"]["state"] == "candidate"
assert detail["research"]["tags"] == ["docker-verified"]
print("PASS: container replacement preserves session and data; snapshot update preserves research")
# This synthetic service runs inside the disposable backend container. No real provider is used.
model_server = Path(__file__).with_name("model_protocol.py").read_text()
def start_model():
run(["exec", "-d", "-T", "backend", "python", "-c", model_server])
run(
[
"exec",
"-T",
"backend",
"python",
"-c",
"import socket,time\nfor _ in range(40):\n try:\n socket.create_connection(('127.0.0.1',19010),timeout=1).close();break\n except OSError: time.sleep(.1)\nelse: raise RuntimeError('Mock model did not start')",
]
)
start_model()
for protocol in ("chat_completions", "responses"):
config = {"base_url": "http://127.0.0.1:19010/v1", "model": "mock-model", "protocol": protocol}
json.load(request("/api/v1/ai/settings", "PUT", config | {"api_key": "synthetic-model-key"}))
tested = json.load(request("/api/v1/ai/settings/test", "POST"))
assert tested["ready"], tested
json.load(request("/api/v1/ai/settings", "PUT", config | {"enabled": True}))
conversation = json.load(request("/api/v1/ai/conversations", "POST"))["id"]
started = time.monotonic()
stream = request(
f"/api/v1/ai/conversations/{conversation}/runs", "POST", {"request_id": "stream", "message": "SLOW"}
)
run_id = stream.headers["X-AI-Run-ID"]
assert stream.headers["x-vercel-ai-ui-message-stream"] == "v1"
while b'"text-delta"' not in stream.readline():
assert time.monotonic() - started < 8
assert json.load(request(f"/api/v1/ai/runs/{run_id}"))["status"] == "running"
stream.close()
for _ in range(40):
if json.load(request(f"/api/v1/ai/runs/{run_id}"))["status"] == "completed":
break
time.sleep(0.25)
else:
raise AssertionError("Disconnected execution did not complete")
stream = request(
f"/api/v1/ai/conversations/{conversation}/runs",
"POST",
{"request_id": "write", "message": "修改研究记录"},
)
run_id = stream.headers["X-AI-Run-ID"]
stream.read()
pending = json.load(request(f"/api/v1/ai/runs/{run_id}"))
assert pending["status"] == "waiting_approval", pending
assert (
json.load(request("/api/v1/alphas/DOCKER_ACCEPTANCE"))["research"]["note"] == "persistent local note"
)
run(["up", "-d", "--force-recreate", "--wait"])
start_model()
assert json.load(request("/api/v1/ai/settings"))["ready"]
approval = pending["tools"][0]["id"]
for _ in range(2):
request(f"/api/v1/ai/approvals/{approval}/decision", "POST", {"approved": True}).read()
saved = json.load(request("/api/v1/alphas/DOCKER_ACCEPTANCE"))["research"]
assert saved["note"] == "AI verified note" and saved["version"] == 3
try:
request("/api/v1/alphas/DOCKER_ACCEPTANCE/research", "PATCH", {"version": 2, "note": "stale"})
raise AssertionError("A stale edit overwrote AI research")
except urllib.error.HTTPError as error:
assert error.code == 409
print(
"PASS: both real SDK protocols over mock HTTP; Caddy streams before completion; disconnect recovery"
)
print(
"PASS: pending approval survives container replacement; duplicate confirmation writes once; PostgreSQL version conflict"
)
dump = run(["exec", "-T", "db", "pg_dump", "-U", "wq", "-d", "wq", "-Fc", "--no-owner"])
backup = Path(".local/docker-acceptance.dump")
fd = os.open(backup, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
@@ -147,8 +220,57 @@ asyncio.run(seed())
.decode()
.strip()
)
assert rows == "persistent local note|candidate"
assert rows == "AI verified note|candidate"
ai_rows = (
run(
[
"exec",
"-T",
"db",
"psql",
"-U",
"wq",
"-d",
"wq_acceptance_restore",
"-At",
"-c",
"SELECT (SELECT count(*) FROM ai_conversations), (SELECT count(*) FROM ai_runs), (SELECT count(*) FROM ai_tool_calls), (SELECT count(*) FROM ai_settings WHERE api_key_encrypted IS NOT NULL)",
]
)
.decode()
.strip()
)
assert ai_rows == "1|2|1|1", ai_rows
print("PASS: PostgreSQL custom-format backup restores records into independent database")
# Downgrade only the scratch restore database, then verify upgrading existing 0001 research.
migration_env = (
"DATABASE_URL=postgresql+asyncpg://wq:"
+ values["POSTGRES_PASSWORD"]
+ "@db:5432/wq_acceptance_restore"
)
for command in (["downgrade", "0001"], ["upgrade", "head"], ["check"]):
run(["exec", "-T", "-e", migration_env, "backend", "alembic", *command])
versioned = (
run(
[
"exec",
"-T",
"db",
"psql",
"-U",
"wq",
"-d",
"wq_acceptance_restore",
"-At",
"-c",
"SELECT note || '|' || version FROM research WHERE alpha_id='DOCKER_ACCEPTANCE'",
]
)
.decode()
.strip()
)
assert versioned == "AI verified note|1", versioned
print("PASS: existing 0001 research upgrades with version 1 and unchanged content; Alembic model parity")
config = json.loads(run(["-f", "compose.public.yaml", "config", "--format", "json"]))
assert config["services"]["backend"]["environment"]["COOKIE_SECURE"] == "true"
assert config["services"]["backend"]["environment"]["PUBLIC_ORIGIN"].startswith("https://")