Files
inquiry_robot/inquiry-agent/agent/foundation_selftest.py
T
jillion886andCursor 99da1faf30 落地私聊空运询价与TMS报价回填,按后台模板出单。
查到价后把币种、单位、数量、单价、备注写入报价单,避免销售只看到光秃秃的金额。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 20:25:35 +08:00

269 lines
10 KiB
Python
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.
"""
基础框架联合自检(不连真实 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()))
# 主账可达时返回 2xx;不可达时 status_code=0 + error,不再假装 501
assert isinstance(lt.status_code, int)
assert lt.ok or lt.error or lt.data is not None
logger.info(
"OK: list_quote_templates status=%s ok=%s err=%s",
lt.status_code,
lt.ok,
(lt.error or "")[:80],
)
vf = validate_required_fields_shell(facts={}, require_formal=True)
assert vf.get("implemented") is True
_ = 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())