feat(collection): doc_ids accepts str|list, design cleanups

- Collection.query and Backend.query/query_stream accept doc_ids as
  str, list[str] or None. Single str is normalized to [str] inside each
  backend; bare [] is rejected with ValueError at both layers.
- wrap_with_doc_context wraps the scoped doc list in <docs>...</docs>
  and SCOPED_SYSTEM_PROMPT instructs the agent to treat that block as
  data, not instructions (defense against prompt injection via
  auto-generated doc_description).
- _require_cloud_api now distinguishes api_key="" from api_key=None;
  the former gives a targeted error pointing at the empty-string vs
  fall-back-to-local situation when legacy SDK methods are called.
- Legacy PageIndexClient.list_documents docstring spells out the
  return-shape difference vs collection.list_documents() to flag a
  silent migration footgun (paginated dict with id/name keys vs plain
  list[dict] with doc_id/doc_name keys).
- Remove dead CloudBackend.get_agent_tools stub (not on the Backend
  protocol; only ever returned an empty AgentTools()) and the
  SYSTEM_PROMPT alias (OPEN_/SCOPED_SYSTEM_PROMPT are the explicit
  names now).
- README quick start and streaming example now pass doc_ids; new
  multi-document section shows both str and list forms.
- examples/demo_query_modes.py exercises all five query-mode cases
  (single-doc, multi-doc with/without env var, scoped single, scoped
  multi) for manual verification.
This commit is contained in:
mountain 2026-05-15 17:03:17 +08:00
parent d7b36aaf3f
commit a47c36a3f5
13 changed files with 322 additions and 45 deletions

View file

@ -13,6 +13,32 @@ def test_cloud_client_is_pageindex_client():
assert isinstance(client, PageIndexClient)
def test_empty_api_key_legacy_method_error_is_specific(tmp_path, caplog):
"""Empty api_key falls back to local mode; legacy methods raise a clear error."""
import warnings
from pageindex.errors import PageIndexAPIError
client = PageIndexClient(api_key="", storage_path=str(tmp_path / "pi"))
# Empty api_key → local mode; legacy methods should explain why
with warnings.catch_warnings():
warnings.simplefilter("ignore", PendingDeprecationWarning)
with pytest.raises(PageIndexAPIError, match="empty string"):
client.submit_document("some.pdf")
def test_none_api_key_legacy_method_error_is_generic(tmp_path):
"""api_key=None → local mode; legacy methods raise generic error (not 'empty')."""
import warnings
from pageindex.errors import PageIndexAPIError
client = PageIndexClient(api_key=None, model="gpt-4o", storage_path=str(tmp_path / "pi"))
with warnings.catch_warnings():
warnings.simplefilter("ignore", PendingDeprecationWarning)
with pytest.raises(PageIndexAPIError) as exc_info:
client.submit_document("some.pdf")
assert "empty" not in str(exc_info.value)
def test_collection_default_name(tmp_path):
client = LocalClient(model="gpt-4o", storage_path=str(tmp_path / "pi"))
col = client.collection()