落地并存测服全端:智能体 Graph/路由、后台切 8180、主账与部署脚本齐备,便于线上重发与验收。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-09-13 21:15:58 +08:00
co-authored by Cursor
parent 0eb1adecc8
commit 8103b3b651
143 changed files with 8968 additions and 277 deletions
+262
View File
@@ -0,0 +1,262 @@
"""
基础框架联合自检(不连真实 PG/外网模型也可跑)。
覆盖:迁移、正式合同、ledger、llm、DeepSeek A 解析、LibreOffice 探测、
识别/存档入队、routing、storage。
用法:
set REDIS_BACKEND=memory
set CHECKPOINT_BACKEND=memory
python -m agent.foundation_selftest
"""
from __future__ import annotations
import logging
import os
import sys
logger = logging.getLogger(__name__)
def main() -> int:
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
os.environ["REDIS_BACKEND"] = "memory"
os.environ.setdefault("YTD_ENV", "test")
os.environ.setdefault("CHECKPOINT_BACKEND", "memory")
os.environ.setdefault("REDIS_KEY_PREFIX", "inquiry_robot:")
os.environ["LLM_DATA_USAGE_CONFIRMED"] = "false"
os.environ["LLM_ALLOW_NETWORK"] = "false"
from agent.config import get_settings
from agent.jobs.archive import enqueue_archive
from agent.jobs.libreoffice import LibreOfficeConverter
from agent.jobs.recognition import enqueue_recognition
from agent.ledger import LedgerClient
from agent.llm import LlmGateway, LlmMode
from agent.redis_coord import shutdown_redis_runtime
from agent.routing import IntentRouter, ordered_actions_tool_definition, parse_ordered_actions_arguments
from agent.runtime_db import assert_safe_database, list_migration_files
from agent.schema import load_contract, require_formal_contract
from agent.storage import ObjectStorageClient
get_settings.cache_clear()
settings = get_settings()
files = list_migration_files()
assert "001_runtime_core_v1.sql" in files, files
assert "002_dead_letter_v1.sql" in files, files
logger.info("OK: migrations %s", files)
try:
assert_safe_database("postgresql://u:p@127.0.0.1:5432/ytd_runtime")
logger.error("应拒绝 ytd_runtime")
return 1
except RuntimeError:
logger.info("OK: 拒绝现网库名 ytd_runtime")
contract = load_contract("inquiry-required-fields-v1")
assert contract["_meta"]["is_placeholder"] is False, contract["_meta"]
formal = require_formal_contract("inquiry-required-fields-v1")
assert formal.get("contract_id") or formal.get("schema_version") is not None
tms = require_formal_contract("tms-dictionary-projection-v2")
assert tms["_meta"]["is_placeholder"] is False
logger.info("OK: 正式合同已加载 fields+tms")
tool = ordered_actions_tool_definition()
assert tool["function"]["name"] == "route_decision"
digest = "a" * 64
parsed = parse_ordered_actions_arguments(
{
"decision_id": "D-1",
"intent": "ordinary_text_inquiry",
"ordered_action": {
"action_type": "ORDINARY_TEXT_INQUIRY",
"order": 1,
"target": {"work_order_no": ""},
"evidence": {"source_ref": "inbound:u1", "content_digest": digest},
},
}
)
assert parsed.intent == "ordinary_text_inquiry"
logger.info("OK: ordered_actions 解析")
ledger = LedgerClient.from_settings(settings)
assert "8180" in ledger.base_url or ledger.base_url
logger.info("OK: ledger client base=%s", ledger.base_url)
ping = ledger.ping()
logger.info("ledger.ping ok=%s status=%s", ping.ok, ping.status_code)
ledger.close()
gw = LlmGateway.from_settings(settings)
enq = gw.enqueue(
mode=LlmMode.ORDERED_ACTIONS,
payload={"text": "hi"},
idempotency_key="foundation-1",
)
assert enq.accepted, enq
stub = gw.invoke_stub(mode=LlmMode.EXTRACT_FIELDS, payload={"a": 1})
assert stub.get("stub") is True
assert stub.get("error") == "extract_fields_shell_not_implemented"
from agent.llm import list_mode_shells
assert "vision" in list_mode_shells()
logger.info("OK: llm enqueue/mode shells")
decision = IntentRouter().route_stub(text="询价", sender_id="u1")
assert decision.intent == "ordinary_text_inquiry", decision.intent
assert IntentRouter().route(text="询价", sender_id="").intent == "reject_no_sender"
logger.info("OK: DeepSeek A stub 路由")
from agent.channel.clarification import issue_token, validate_token
from agent.channel.queue import reset_message_store_for_tests
from agent.channel.wecom.models import InboundMessage
reset_message_store_for_tests()
os.environ["MESSAGE_STORE_BACKEND"] = "memory"
get_settings.cache_clear()
reset_message_store_for_tests()
from agent.channel.queue import get_message_store
store = get_message_store()
assert store.stats().get("backend") == "memory"
msg = InboundMessage(sender_id="u1", message_id="m-foundation-1", content="hello")
ok, iid = store.enqueue_inbound(msg)
assert ok, iid
claimed = store.claim_inbound()
assert claimed is not None
store.complete_inbound(claimed.id)
logger.info("OK: memory inbox claim")
tok = issue_token(
thread_id="t1",
wait_version=1,
sender_id="u1",
allowed_actions=["submit_clarify"],
)
rec = validate_token(tok, action="submit_clarify", consume=True)
assert rec is not None and rec.thread_id == "t1"
assert validate_token(tok) is None
logger.info("OK: clarification token")
from agent.graph import GraphRuntime, shutdown_graph_runtime
from agent.graph.wake import WorkerWakePayload
from agent.jobs.wake_consumer import claim_and_apply_wake, enqueue_wake, wake_stream
from agent.llm import LlmHttpClient
from agent.redis_coord import get_redis_runtime
os.environ["CHECKPOINT_BACKEND"] = "memory"
get_settings.cache_clear()
gr = GraphRuntime.create(get_settings())
started = gr.start_thread(sender_id="u1")
tid = started["thread_id"]
rr = get_redis_runtime(get_settings())
entry = enqueue_wake(
rr.client,
WorkerWakePayload(
thread_id=tid,
inquiry_no="F-1",
quote_version=1,
wait_version=0,
kind="recognition_done",
),
)
assert entry
q = wake_stream(rr.client)
assert claim_and_apply_wake(queue=q, graph=gr, consumer="selftest")
logger.info("OK: wake enqueue/apply")
http_llm = LlmHttpClient.from_settings(get_settings())
no_net = http_llm.chat(mode="ordered_actions", messages=[{"role": "user", "content": "hi"}])
assert no_net.ok is False and no_net.error == "llm_data_usage_not_confirmed"
logger.info("OK: llm http 拒绝未确认数据用途")
recog = enqueue_recognition(
kind="attachment_text",
payload={"object_key": "stub"},
idempotency_key="recog-1",
redis=rr,
)
assert recog.accepted, recog
arch = enqueue_archive(payload={"msg_id": "m1"}, idempotency_key="arch-1", redis=rr)
assert arch.accepted, arch
logger.info("OK: recognition/archive enqueue")
lo = LibreOfficeConverter.from_settings(get_settings())
logger.info("OK: LibreOffice available=%s path=%s", lo.available(), lo.executable())
gr.close()
shutdown_graph_runtime()
storage = ObjectStorageClient.from_settings(settings)
put_ok = storage.put_bytes_stub(key="x", data=b"1")
assert put_ok is storage.configured()
logger.info("OK: storage stub configured=%s put_ok=%s", storage.configured(), put_ok)
from agent.jobs.dead_letter import enqueue_dead_letter
from agent.jobs.pg_job import create_job_store
from agent.ledger.api_shell import LedgerApiShell
from agent.policy import assert_has_sender_id, policy_package_ready
from agent.quote_templates import list_template_registry
from agent.routing.dispatch import HANDLER_REGISTRY, dispatch_inbound
from agent.runtime_db import SessionRecord, create_session_store, create_suspend_store
from agent.runtime_db.suspend_store import SuspendRecord
from agent.schema import validate_required_fields_shell
from agent.storage.s3_client import S3ObjectStorage
assert policy_package_ready()
assert assert_has_sender_id("u1")["ok"] is True
assert assert_has_sender_id("")["ok"] is False
sess = create_session_store(backend="memory")
sess.upsert(SessionRecord(thread_id="t-shell", sender_id="u1"))
assert sess.get("t-shell") is not None
sus = create_suspend_store(backend="memory")
sus.open_wait(SuspendRecord(thread_id="t-shell", wait_version=1, pause_name="pause_parse_clarify"))
assert sus.get("t-shell", 1) is not None
assert sus.close_wait("t-shell", 1) is True
jobs = create_job_store(backend="memory")
ok_j, jid = jobs.enqueue(job_type="shell", payload={"x": 1}, idempotency_key="j1")
assert ok_j, jid
claimed_j = jobs.claim(worker_id="selftest")
assert claimed_j is not None
jobs.complete(claimed_j.id)
dl = enqueue_dead_letter(source="selftest", error="demo", job_type="shell")
assert dl
assert "ordinary_text_inquiry" in HANDLER_REGISTRY
d2 = IntentRouter().route_stub(text="x", sender_id="u1")
msg2 = InboundMessage(sender_id="u1", message_id="m-dispatch", content="x")
# dispatch 可能写 outbox(echo);仅验证查表调用不抛
dispatch_inbound(msg2, d2)
shell_api = LedgerApiShell(LedgerClient.from_settings(get_settings()))
assert shell_api.create_ticket(payload={}, idempotency_key="x").status_code == 501
from agent.quote_templates import list_templates_from_ledger
lt = list_templates_from_ledger(LedgerClient.from_settings(get_settings()))
assert lt.status_code == 501
assert lt.error and "list_quote_templates" in lt.error
vf = validate_required_fields_shell(facts={}, require_formal=True)
assert vf.get("implemented") is False
_ = list_template_registry()
s3 = S3ObjectStorage.from_settings(get_settings(), allow_network=False)
put_s3 = s3.put_bytes(key="k", data=b"1")
assert put_s3.get("dry_run") is True or put_s3.get("error") in {
"not_configured",
"boto3_not_installed",
}
logger.info("OK: session/suspend/job/dead_letter/dispatch/ledger/schema/s3 shells")
shutdown_redis_runtime()
logger.info("基础框架联合自检通过")
return 0
if __name__ == "__main__":
sys.exit(main())