落地并存测服全端:智能体 Graph/路由、后台切 8180、主账与部署脚本齐备,便于线上重发与验收。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user