Files
RAG/backend/app/tools/seed_demo.py
YoVinchen 75592af33a
Some checks failed
verify / verify (push) Has been cancelled
Isolate cloud model access before enabling product RAG workflows
The API and ingestion tools now use a fixed internal model gateway while
governed profiles, embedding cache assignments, traceable citations, and
stable API errors establish the boundaries required by later workflows.

Constraint: The current Alibaba Cloud workspace rejects all three live model calls with authentication failures
Rejected: Give the API or seed tools the Bailian key and direct egress | combines database access, cloud credentials, and public network access
Rejected: Mix offline and Bailian vectors in one demo namespace | makes profile activation and retrieval ambiguous
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep Bailian credentials and egress exclusive to model-gateway and create a new immutable profile hash for any embedding identity change
Tested: make verify; 121 backend tests; 14 frontend tests; fresh and populated Alembic upgrade-downgrade-upgrade; two idempotent offline seeds; Docker health and HTTP retrieval; isolated provider smoke
Not-tested: Successful live Bailian responses because the supplied workspace credential currently fails authentication
2026-07-13 04:09:06 +08:00

866 lines
31 KiB
Python

"""Idempotently embed, store, retrieve, and rerank the public synthetic corpus."""
from __future__ import annotations
import asyncio
import hashlib
import json
import os
import sys
import uuid
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any, cast
from urllib.parse import urlsplit
import psycopg
from pgvector import Vector
from pgvector.psycopg import register_vector
from psycopg.rows import dict_row
from psycopg.types.json import Jsonb
from app.adapters.fake import FakeEmbeddingProvider, FakeReranker, lexical_features
from app.adapters.model_gateway import ModelGatewayAdapter
from app.core.config import Settings
from app.core.demo_identity import (
ACCESS_SCOPE_ID,
IDENTITY_NAMESPACE,
KNOWLEDGE_BASE_ID,
offline_embedding_profile_hash,
)
from app.core.secrets import SecretFileError
from app.ports.model_providers import EmbeddingProvider, ModelProviderError, Reranker
PROJECT_ROOT = Path(__file__).resolve().parents[3]
DEFAULT_SAMPLE_ROOT = PROJECT_ROOT / "data" / "samples" / "public"
@dataclass(frozen=True, slots=True)
class DemoDocument:
source_id: str
title: str
content: str
region: str
mineral: str
page_no: int
cloud_policy_id: str
@dataclass(frozen=True, slots=True)
class DemoQuery:
qid: str
query: str
expected_doc_ids: tuple[str, ...]
answerable: bool
@dataclass(frozen=True, slots=True)
class DemoNamespace:
mode: str
knowledge_base_id: uuid.UUID
access_scope_id: uuid.UUID
scope_name: str
knowledge_base_name: str
storage_prefix: str
OFFLINE_NAMESPACE = DemoNamespace(
mode="fake",
knowledge_base_id=KNOWLEDGE_BASE_ID,
access_scope_id=ACCESS_SCOPE_ID,
scope_name="synthetic-demo",
knowledge_base_name="虚构地质 PoC 知识库(离线)",
storage_prefix="synthetic/offline",
)
BAILIAN_NAMESPACE = DemoNamespace(
mode="bailian",
knowledge_base_id=uuid.uuid5(IDENTITY_NAMESPACE, "synthetic-bailian-knowledge-base"),
access_scope_id=uuid.uuid5(IDENTITY_NAMESPACE, "synthetic-bailian-public-scope"),
scope_name="synthetic-bailian-validation",
knowledge_base_name="虚构地质 PoC 知识库(百炼验证)",
storage_prefix="synthetic/bailian",
)
@dataclass(frozen=True, slots=True)
class EmbeddedVector:
vector: tuple[float, ...]
request_id: str | None
usage: dict[str, int | None]
elapsed_ms: int
@dataclass(frozen=True, slots=True)
class PreparedChunk:
source_id: str
document_id: uuid.UUID
version_id: uuid.UUID
chunk_id: uuid.UUID
raw_sha256: str
cloud_text: str
cloud_text_sha256: str
embedding_prefix: str
embedding_text: str
embedding_text_sha256: str
outbound_manifest_sha256: str
embedding_profile_hash: str
vector: tuple[float, ...]
embedding_model: str
provider_request_id: str | None
embedding_usage: dict[str, int | None]
embedding_elapsed_ms: int
title: str
region: str
mineral: str
page_no: int
cloud_policy_id: str
class SeedContractError(ValueError):
def __init__(self, code: str) -> None:
self.code = code
super().__init__(code)
def sha256_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def namespace_for_mode(mode: str) -> DemoNamespace:
if mode == "fake":
return OFFLINE_NAMESPACE
if mode == "bailian":
return BAILIAN_NAMESPACE
raise SeedContractError("invalid_provider_mode")
def load_jsonl(path: Path) -> list[dict[str, Any]]:
if not path.is_file():
raise SeedContractError("fixture_missing")
records: list[dict[str, Any]] = []
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
try:
value = json.loads(line)
except json.JSONDecodeError as exc:
raise SeedContractError(f"invalid_jsonl_line_{line_number}") from exc
if not isinstance(value, dict):
raise SeedContractError(f"jsonl_object_required_line_{line_number}")
records.append(value)
return records
def load_documents(path: Path) -> list[DemoDocument]:
documents = []
for value in load_jsonl(path):
if value.get("source_type") != "synthetic":
raise SeedContractError("non_synthetic_document")
if value.get("review_state") != "LOCAL_PARSED_PENDING_CLOUD_REVIEW":
raise SeedContractError("invalid_initial_review_state")
documents.append(
DemoDocument(
source_id=str(value["doc_id"]),
title=str(value["title"]),
content=str(value["content"]),
region=str(value["region"]),
mineral=str(value["mineral"]),
page_no=int(value["page_no"]),
cloud_policy_id=str(value["cloud_policy_id"]),
)
)
if len(documents) != 20 or len({item.source_id for item in documents}) != 20:
raise SeedContractError("expected_twenty_unique_documents")
return documents
def load_queries(path: Path) -> list[DemoQuery]:
queries = [
DemoQuery(
qid=str(value["qid"]),
query=str(value["query"]),
expected_doc_ids=tuple(str(item) for item in value["expected_doc_ids"]),
answerable=bool(value["answerable"]),
)
for value in load_jsonl(path)
]
if not queries:
raise SeedContractError("query_set_empty")
return queries
def embedding_profile_hash(settings: Settings, mode: str) -> str:
if mode == "fake":
return offline_embedding_profile_hash(settings.embedding_dimension)
if mode != "bailian":
raise SeedContractError("invalid_provider_mode")
endpoint_identity = sha256_text(urlsplit(settings.bailian_openai_base_url).hostname or "")
model = settings.embedding_model
api_mode = "openai-compatible"
profile = {
"api_mode": api_mode,
"dimension": settings.embedding_dimension,
"endpoint_identity_hash": endpoint_identity,
"model": model,
"normalization": "provider-default",
"profile_version": 1,
}
return sha256_text(
json.dumps(profile, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
)
async def embed_in_batches(
provider: EmbeddingProvider,
texts: Sequence[str],
) -> tuple[tuple[EmbeddedVector, ...], str]:
vectors: list[EmbeddedVector] = []
resolved_model: str | None = None
for offset in range(0, len(texts), 10):
result = await provider.embed_documents(texts[offset : offset + 10])
if resolved_model is not None and result.model != resolved_model:
raise SeedContractError("embedding_model_changed_between_batches")
resolved_model = result.model
if len(result.vectors) != len(texts[offset : offset + 10]):
raise SeedContractError("embedding_batch_count_mismatch")
usage = {
"input_tokens": result.usage.input_tokens,
"output_tokens": result.usage.output_tokens,
"total_tokens": result.usage.total_tokens,
}
vectors.extend(
EmbeddedVector(
vector=vector,
request_id=result.request_id,
usage=usage,
elapsed_ms=max(0, round(result.elapsed_ms)),
)
for vector in result.vectors
)
if len(vectors) != len(texts) or resolved_model is None:
raise SeedContractError("embedding_result_count_mismatch")
return tuple(vectors), resolved_model
def prepare_chunks(
documents: Sequence[DemoDocument],
vectors: Sequence[EmbeddedVector],
*,
profile_hash: str,
embedding_model: str,
namespace: DemoNamespace = OFFLINE_NAMESPACE,
) -> list[PreparedChunk]:
prepared = []
for document, vector in zip(documents, vectors, strict=True):
raw_payload = json.dumps(
{
"content": document.content,
"mineral": document.mineral,
"page_no": document.page_no,
"region": document.region,
"title": document.title,
},
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
raw_hash = sha256_text(raw_payload)
document_identity = (
f"document:{document.source_id}"
if namespace.mode == "fake"
else f"document:{namespace.mode}:{document.source_id}"
)
document_id = uuid.uuid5(IDENTITY_NAMESPACE, document_identity)
version_id = uuid.uuid5(
IDENTITY_NAMESPACE,
f"version:{document.source_id}:{raw_hash}:{profile_hash}",
)
chunk_id = uuid.uuid5(IDENTITY_NAMESPACE, f"chunk:{version_id}:0")
prefix = (
f"标题:{document.title}\n地区:{document.region}\n矿种:{document.mineral}\n正文:"
)
cloud_hash = sha256_text(document.content)
embedding_text = prefix + document.content
embedding_hash = sha256_text(embedding_text)
manifest_payload = json.dumps(
[
{
"cloud_text_sha256": cloud_hash,
"embedding_text_sha256": embedding_hash,
"ordinal": 0,
}
],
sort_keys=True,
separators=(",", ":"),
)
prepared.append(
PreparedChunk(
source_id=document.source_id,
document_id=document_id,
version_id=version_id,
chunk_id=chunk_id,
raw_sha256=raw_hash,
cloud_text=document.content,
cloud_text_sha256=cloud_hash,
embedding_prefix=prefix,
embedding_text=embedding_text,
embedding_text_sha256=embedding_hash,
outbound_manifest_sha256=sha256_text(manifest_payload),
embedding_profile_hash=profile_hash,
vector=vector.vector,
embedding_model=embedding_model,
provider_request_id=vector.request_id,
embedding_usage=vector.usage,
embedding_elapsed_ms=vector.elapsed_ms,
title=document.title,
region=document.region,
mineral=document.mineral,
page_no=document.page_no,
cloud_policy_id=document.cloud_policy_id,
)
)
return prepared
def database_dsn(settings: Settings) -> str:
return (
settings.database_url().set(drivername="postgresql").render_as_string(hide_password=False)
)
def write_chunks(
settings: Settings,
chunks: Sequence[PreparedChunk],
*,
namespace: DemoNamespace,
) -> dict[str, int]:
if not chunks:
raise SeedContractError("chunks_empty")
profile_hashes = {item.embedding_profile_hash for item in chunks}
resolved_models = {item.embedding_model for item in chunks}
if len(profile_hashes) != 1 or len(resolved_models) != 1:
raise SeedContractError("mixed_embedding_profiles")
profile_hash = next(iter(profile_hashes))
resolved_model = next(iter(resolved_models))
if namespace.mode == "fake":
provider = "local-synthetic"
api_mode = "deterministic-offline"
endpoint_identity_hash = sha256_text("local-fake")
else:
provider = "aliyun-bailian"
api_mode = "model-gateway/openai-compatible"
endpoint_identity_hash = sha256_text(
urlsplit(settings.bailian_openai_base_url).hostname or ""
)
with psycopg.connect(database_dsn(settings), row_factory=dict_row) as connection:
register_vector(connection)
connection.execute("SELECT pg_advisory_xact_lock(724202607120001)")
connection.execute(
"""
INSERT INTO rag.model_profiles (
profile_hash, alias, kind, provider, model, api_mode, dimension,
endpoint_identity_hash, config_snapshot, synthetic, enabled
) VALUES (
%s, %s, 'embedding', %s, %s, %s, 1024, %s, %s, %s, true
)
ON CONFLICT (profile_hash) DO NOTHING
""",
(
profile_hash,
f"{namespace.mode}-embedding-{profile_hash[:12]}",
provider,
resolved_model,
api_mode,
endpoint_identity_hash,
Jsonb(
{
"dimension": settings.embedding_dimension,
"requested_model": settings.embedding_model,
"source": "synthetic-seed-v1",
}
),
namespace.mode == "fake",
),
)
registered_profile = connection.execute(
"""
SELECT kind, provider, model, api_mode, dimension, endpoint_identity_hash
FROM rag.model_profiles
WHERE profile_hash = %s
""",
(profile_hash,),
).fetchone()
if registered_profile is None or (
registered_profile["kind"] != "embedding"
or registered_profile["provider"] != provider
or registered_profile["model"] != resolved_model
or registered_profile["api_mode"] != api_mode
or registered_profile["dimension"] != settings.embedding_dimension
or registered_profile["endpoint_identity_hash"] != endpoint_identity_hash
):
raise SeedContractError("embedding_profile_collision")
connection.execute(
"""
INSERT INTO rag.knowledge_bases (
id, name, description, active_embedding_profile_hash
)
VALUES (%s, %s, %s, %s)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
description = EXCLUDED.description,
active_embedding_profile_hash = EXCLUDED.active_embedding_profile_hash,
updated_at = now()
""",
(
namespace.knowledge_base_id,
namespace.knowledge_base_name,
"仅含公开的合成验证文本",
profile_hash,
),
)
connection.execute(
"""
INSERT INTO rag.access_scopes (id, knowledge_base_id, name)
VALUES (%s, %s, %s)
ON CONFLICT (id) DO NOTHING
""",
(
namespace.access_scope_id,
namespace.knowledge_base_id,
namespace.scope_name,
),
)
for item in chunks:
connection.execute(
"""
INSERT INTO rag.documents (
id, knowledge_base_id, access_scope_id, raw_sha256,
filename, storage_key, mime_type, status
) VALUES (%s, %s, %s, %s, %s, %s, 'application/json',
'LOCAL_PARSED_PENDING_CLOUD_REVIEW')
ON CONFLICT (id) DO UPDATE
SET filename = EXCLUDED.filename,
storage_key = EXCLUDED.storage_key,
mime_type = EXCLUDED.mime_type,
updated_at = now()
""",
(
item.document_id,
namespace.knowledge_base_id,
namespace.access_scope_id,
item.raw_sha256,
f"{item.source_id}.json",
f"{namespace.storage_prefix}/{item.source_id}",
),
)
connection.execute(
"""
INSERT INTO rag.document_versions (
id, document_id, parser_profile_hash, ocr_profile_hash,
normalization_profile_hash, chunk_profile_hash, cloud_policy_id,
outbound_manifest_sha256, review_state, embedding_profile_hash,
status, expected_chunk_count
) VALUES (
%s, %s, %s, NULL, %s, %s, %s, %s,
'LOCAL_PARSED_PENDING_CLOUD_REVIEW', %s, 'PROCESSING', 1
)
ON CONFLICT (id) DO NOTHING
""",
(
item.version_id,
item.document_id,
sha256_text("synthetic-jsonl-parser-v1"),
sha256_text("identity-normalization-v1"),
sha256_text("one-record-one-chunk-v1"),
item.cloud_policy_id,
item.outbound_manifest_sha256,
item.embedding_profile_hash,
),
)
connection.execute(
"""
INSERT INTO rag.outbound_manifest_items (
document_version_id, ordinal, outbound_manifest_sha256,
cloud_text_sha256, embedding_text_sha256
)
SELECT %s, 0, %s, %s, %s
WHERE NOT EXISTS (
SELECT 1
FROM rag.outbound_manifest_items
WHERE document_version_id = %s AND ordinal = 0
)
ON CONFLICT (document_version_id, ordinal) DO NOTHING
""",
(
item.version_id,
item.outbound_manifest_sha256,
item.cloud_text_sha256,
item.embedding_text_sha256,
item.version_id,
),
)
connection.execute(
"""
INSERT INTO rag.chunks (
id, knowledge_base_id, document_id, document_version_id,
access_scope_id, ordinal, display_text, cloud_text,
cloud_text_sha256, embedding_prefix, embedding_text,
embedding_text_sha256, embedded_text_sha256,
embedding_profile_hash, outbound_manifest_sha256, token_count,
page_start, page_end, section_path, metadata, embedding_model,
embedding_dimension, embedding, approval_status, index_status,
searchable
) VALUES (
%s, %s, %s, %s, %s, 0, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, 1024, %s,
'LOCAL_PARSED_PENDING_CLOUD_REVIEW', 'READY', false
)
ON CONFLICT (id) DO UPDATE SET
display_text = EXCLUDED.display_text,
cloud_text = EXCLUDED.cloud_text,
cloud_text_sha256 = EXCLUDED.cloud_text_sha256,
embedding_prefix = EXCLUDED.embedding_prefix,
embedding_text = EXCLUDED.embedding_text,
embedding_text_sha256 = EXCLUDED.embedding_text_sha256,
embedded_text_sha256 = EXCLUDED.embedded_text_sha256,
embedding_profile_hash = EXCLUDED.embedding_profile_hash,
outbound_manifest_sha256 = EXCLUDED.outbound_manifest_sha256,
token_count = EXCLUDED.token_count,
page_start = EXCLUDED.page_start,
page_end = EXCLUDED.page_end,
section_path = EXCLUDED.section_path,
metadata = EXCLUDED.metadata,
embedding_model = EXCLUDED.embedding_model,
updated_at = now()
""",
(
item.chunk_id,
namespace.knowledge_base_id,
item.document_id,
item.version_id,
namespace.access_scope_id,
item.cloud_text,
item.cloud_text,
item.cloud_text_sha256,
item.embedding_prefix,
item.embedding_text,
item.embedding_text_sha256,
item.embedding_text_sha256,
item.embedding_profile_hash,
item.outbound_manifest_sha256,
max(1, len(lexical_features(item.embedding_text))),
item.page_no,
item.page_no,
Jsonb([item.title]),
Jsonb(
{
"mineral": item.mineral,
"region": item.region,
"source_doc_id": item.source_id,
"source_type": "synthetic",
}
),
item.embedding_model,
Vector(list(item.vector)),
),
)
connection.execute(
"""
UPDATE rag.document_versions
SET review_state = 'CLOUD_APPROVED',
cloud_approved_at = COALESCE(cloud_approved_at, now()),
cloud_approved_by = 'seed-demo:synthetic-policy',
status = 'READY',
completed_at = COALESCE(completed_at, now())
WHERE id = %s
""",
(item.version_id,),
)
connection.execute(
"""
INSERT INTO rag.embedding_cache (
profile_hash, embedding_text_sha256, embedding, resolved_model,
provider_request_id, usage, elapsed_ms
) VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (profile_hash, embedding_text_sha256) DO NOTHING
""",
(
item.embedding_profile_hash,
item.embedding_text_sha256,
Vector(list(item.vector)),
item.embedding_model,
item.provider_request_id,
Jsonb(item.embedding_usage),
item.embedding_elapsed_ms,
),
)
connection.execute(
"""
INSERT INTO rag.chunk_embedding_assignments (
chunk_id, profile_hash, embedding_text_sha256,
cache_profile_hash, cache_embedding_text_sha256,
status, completed_at
) VALUES (%s, %s, %s, %s, %s, 'READY', now())
ON CONFLICT (chunk_id, profile_hash) DO NOTHING
""",
(
item.chunk_id,
item.embedding_profile_hash,
item.embedding_text_sha256,
item.embedding_profile_hash,
item.embedding_text_sha256,
),
)
connection.execute(
"""
UPDATE rag.chunks
SET approval_status = 'CLOUD_APPROVED',
index_status = 'READY',
updated_at = now()
WHERE id = %s
""",
(item.chunk_id,),
)
connection.execute(
"""
UPDATE rag.documents
SET active_version_id = %s,
raw_sha256 = %s,
status = 'READY',
updated_at = now()
WHERE id = %s
""",
(item.version_id, item.raw_sha256, item.document_id),
)
connection.execute(
"UPDATE rag.chunks SET searchable = true, updated_at = now() WHERE id = %s",
(item.chunk_id,),
)
counts = connection.execute(
"""
SELECT
count(*)::integer AS chunks,
count(*) FILTER (WHERE embedding IS NOT NULL)::integer AS vectors,
count(*) FILTER (WHERE searchable)::integer AS searchable
FROM rag.chunks
WHERE knowledge_base_id = %s
AND embedding_profile_hash = %s
""",
(namespace.knowledge_base_id, profile_hash),
).fetchone()
if counts is None:
raise SeedContractError("database_count_missing")
return {key: int(counts[key]) for key in ("chunks", "vectors", "searchable")}
def retrieve(
settings: Settings,
query_vector: tuple[float, ...],
*,
namespace: DemoNamespace,
profile_hash: str,
) -> list[dict[str, Any]]:
with psycopg.connect(database_dsn(settings), row_factory=dict_row) as connection:
register_vector(connection)
connection.execute("SET LOCAL hnsw.iterative_scan = strict_order")
connection.execute("SET LOCAL hnsw.ef_search = 100")
rows = connection.execute(
"""
SELECT chunk.id, chunk.metadata, chunk.embedding_text,
1 - (chunk.embedding <=> %s) AS vector_score
FROM rag.chunks AS chunk
JOIN rag.knowledge_bases AS knowledge_base
ON knowledge_base.id = chunk.knowledge_base_id
AND knowledge_base.active_embedding_profile_hash = %s
WHERE chunk.searchable
AND chunk.knowledge_base_id = %s
AND chunk.access_scope_id = %s
AND chunk.embedding_profile_hash = %s
ORDER BY chunk.embedding <=> %s
LIMIT %s
""",
(
Vector(list(query_vector)),
profile_hash,
namespace.knowledge_base_id,
namespace.access_scope_id,
profile_hash,
Vector(list(query_vector)),
settings.vector_top_k,
),
).fetchall()
return [dict(row) for row in rows]
async def evaluate_queries(
settings: Settings,
queries: Sequence[DemoQuery],
embedder: EmbeddingProvider,
reranker: Reranker,
*,
namespace: DemoNamespace,
profile_hash: str,
) -> dict[str, float | int]:
hits = 0
answerable = 0
for query in queries:
query_result = await embedder.embed_query(query.query)
candidates = retrieve(
settings,
query_result.vectors[0],
namespace=namespace,
profile_hash=profile_hash,
)
if not candidates:
continue
reranked = await reranker.rerank(
query.query,
[cast(str, item["embedding_text"]) for item in candidates],
top_n=min(settings.rerank_top_n, len(candidates)),
)
result_doc_ids = [
cast(dict[str, Any], candidates[item.index]["metadata"])["source_doc_id"]
for item in reranked.items[:3]
]
if query.answerable:
answerable += 1
if set(query.expected_doc_ids) & set(result_doc_ids):
hits += 1
return {
"answerable_queries": answerable,
"hit_at_3": hits,
"hit_rate_at_3": round(hits / answerable, 4) if answerable else 0.0,
}
def output_summary(payload: dict[str, Any]) -> None:
sys.stdout.write(json.dumps(payload, ensure_ascii=False, sort_keys=True) + "\n")
def safe_failure_site(error: BaseException) -> str:
traceback = error.__traceback__
selected: str | None = None
while traceback is not None:
filename = Path(traceback.tb_frame.f_code.co_filename).name
if filename == "seed_demo.py":
selected = f"{filename}:{traceback.tb_frame.f_code.co_name}:{traceback.tb_lineno}"
traceback = traceback.tb_next
return selected or "external_dependency"
async def async_main() -> int:
mode = os.getenv("DEMO_PROVIDER_MODE", "fake").strip().lower()
if mode not in {"fake", "bailian"}:
output_summary({"status": "failed", "error_kind": "invalid_provider_mode"})
return 2
settings = Settings()
namespace = namespace_for_mode(mode)
documents_path = Path(
os.getenv("DEMO_DOCUMENTS_PATH", str(DEFAULT_SAMPLE_ROOT / "demo_documents.jsonl"))
)
queries_path = Path(
os.getenv("DEMO_QUERIES_PATH", str(DEFAULT_SAMPLE_ROOT / "demo_queries.jsonl"))
)
cloud_gateway: ModelGatewayAdapter | None = None
try:
documents = load_documents(documents_path)
queries = load_queries(queries_path)
profile_hash = embedding_profile_hash(settings, mode)
embedder: EmbeddingProvider
reranker: Reranker
if mode == "bailian":
cloud_gateway = ModelGatewayAdapter.from_settings(settings)
embedder = cloud_gateway
reranker = cloud_gateway
else:
embedder = FakeEmbeddingProvider(settings.embedding_dimension)
reranker = FakeReranker()
texts = [
f"标题:{item.title}\n地区:{item.region}\n矿种:{item.mineral}\n正文:{item.content}"
for item in documents
]
vectors, resolved_model = await embed_in_batches(embedder, texts)
prepared = prepare_chunks(
documents,
vectors,
profile_hash=profile_hash,
embedding_model=resolved_model,
namespace=namespace,
)
counts = write_chunks(settings, prepared, namespace=namespace)
metrics = await evaluate_queries(
settings,
queries,
embedder,
reranker,
namespace=namespace,
profile_hash=profile_hash,
)
output_summary(
{
"counts": counts,
"embedding_model": resolved_model,
"metrics": metrics,
"provider_mode": mode,
"status": "ok",
}
)
return 0
except ModelProviderError as exc:
output_summary(
{
"status": "failed",
"error_kind": f"model_provider_{exc.kind.value}",
"status_code": exc.status_code,
}
)
return 1
except psycopg.Error as exc:
constraint_name = exc.diag.constraint_name
output_summary(
{
"status": "failed",
"error_kind": "database_error",
"sqlstate": exc.sqlstate,
"failure_site": safe_failure_site(exc),
"constraint": constraint_name
if constraint_name and constraint_name.replace("_", "").isalnum()
else None,
}
)
return 1
except SecretFileError:
output_summary({"status": "failed", "error_kind": "secret_configuration"})
return 1
except OSError:
output_summary({"status": "failed", "error_kind": "fixture_io_error"})
return 1
except SeedContractError as exc:
output_summary({"status": "failed", "error_kind": "seed_contract_error", "code": exc.code})
return 1
except ValueError as exc:
output_summary(
{
"status": "failed",
"error_kind": "fixture_or_contract_error",
"failure_site": safe_failure_site(exc),
}
)
return 1
finally:
if cloud_gateway is not None:
await cloud_gateway.aclose()
def main() -> None:
raise SystemExit(asyncio.run(async_main()))
if __name__ == "__main__":
main()