pageindex 0.2.10.dev3__tar.gz → 0.2.10.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/PKG-INFO +3 -7
  2. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/README.md +1 -5
  3. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/__init__.py +5 -0
  4. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/client.py +185 -43
  5. pageindex-0.2.10.dev5/pageindex/config.yaml +15 -0
  6. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/local_api.py +21 -8
  7. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/local_chat.py +178 -74
  8. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/page_index_classic.py +0 -1
  9. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/utils.py +85 -11
  10. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pyproject.toml +2 -2
  11. pageindex-0.2.10.dev3/pageindex/config.yaml +0 -13
  12. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/LICENSE +0 -0
  13. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/_version.py +0 -0
  14. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/agent_tools.py +0 -0
  15. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/cloud_api.py +0 -0
  16. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/errors.py +0 -0
  17. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/README.md +0 -0
  18. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/__init__.py +0 -0
  19. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/api.py +0 -0
  20. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/blocks/__init__.py +0 -0
  21. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/blocks/build.py +0 -0
  22. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/blocks/join_rules.py +0 -0
  23. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/classification/__init__.py +0 -0
  24. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/classification/body_text.py +0 -0
  25. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/classification/header_footer.py +0 -0
  26. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/classification/keyword_tables.py +0 -0
  27. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/classification/toc_boilerplate.py +0 -0
  28. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/clustering/__init__.py +0 -0
  29. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/clustering/build.py +0 -0
  30. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/clustering/merge_rules.py +0 -0
  31. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/columns/__init__.py +0 -0
  32. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/columns/gutters.py +0 -0
  33. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/columns/splitting.py +0 -0
  34. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/data/boilerplate_phrases.json +0 -0
  35. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/data/dictionaries.json +0 -0
  36. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/data/glyph_name_table.json +0 -0
  37. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/data/normalized_unicodes.json +0 -0
  38. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/data/script_bucket_table.json +0 -0
  39. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/embedded_toc.py +0 -0
  40. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/__init__.py +0 -0
  41. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/candidates.py +0 -0
  42. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/detectors.py +0 -0
  43. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/keyword_tables.py +0 -0
  44. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/neighbors.py +0 -0
  45. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/page_scan.py +0 -0
  46. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/style_detectors.py +0 -0
  47. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/text_checks.py +0 -0
  48. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/labels/__init__.py +0 -0
  49. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/labels/caption_regions.py +0 -0
  50. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/labels/caption_text.py +0 -0
  51. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/main.py +0 -0
  52. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/model/__init__.py +0 -0
  53. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/model/block.py +0 -0
  54. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/model/char_stats.py +0 -0
  55. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/model/numbering.py +0 -0
  56. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/model/rects.py +0 -0
  57. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/model/span_line.py +0 -0
  58. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline/__init__.py +0 -0
  59. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline/filtering.py +0 -0
  60. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline/tree.py +0 -0
  61. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/__init__.py +0 -0
  62. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/assembly.py +0 -0
  63. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/candidates.py +0 -0
  64. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/cliques.py +0 -0
  65. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/selection.py +0 -0
  66. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/style_context.py +0 -0
  67. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/__init__.py +0 -0
  68. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/char_extract.py +0 -0
  69. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/cmap_parse.py +0 -0
  70. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/code_walk.py +0 -0
  71. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/content_stream.py +0 -0
  72. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/font_unicode.py +0 -0
  73. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/geometry.py +0 -0
  74. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/glyph_tables.py +0 -0
  75. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/merge.py +0 -0
  76. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/pdf_objects.py +0 -0
  77. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/pipeline.py +0 -0
  78. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/remerge.py +0 -0
  79. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/text_normalize.py +0 -0
  80. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/unicode_apply.py +0 -0
  81. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_parallel.py +0 -0
  82. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/phases/__init__.py +0 -0
  83. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/phases/line_numbers.py +0 -0
  84. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/phases/page_view.py +0 -0
  85. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/stats/__init__.py +0 -0
  86. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/stats/aggregates.py +0 -0
  87. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/stats/scripts.py +0 -0
  88. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/title/__init__.py +0 -0
  89. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/title/detect.py +0 -0
  90. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/title/dicts.py +0 -0
  91. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/title/scoring.py +0 -0
  92. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/__init__.py +0 -0
  93. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/hashing.py +0 -0
  94. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/token_types.py +0 -0
  95. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/tokenizer.py +0 -0
  96. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/tries.py +0 -0
  97. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/integrations/__init__.py +0 -0
  98. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/integrations/anthropic_sdk.py +0 -0
  99. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/integrations/claude_agent_sdk.py +0 -0
  100. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/integrations/openai_agents.py +0 -0
  101. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/local_store.py +0 -0
  102. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/mcp_bridge.py +0 -0
  103. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/page_index_md.py +0 -0
  104. {pageindex-0.2.10.dev3 → pageindex-0.2.10.dev5}/pageindex/tree_optimize.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pageindex
3
- Version: 0.2.10.dev3
3
+ Version: 0.2.10.dev5
4
4
  Summary: Python SDK for PageIndex — reasoning-based, vectorless document retrieval, cloud and local
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -24,7 +24,7 @@ Provides-Extra: openai
24
24
  Requires-Dist: PyPDF2 (>=3.0.0)
25
25
  Requires-Dist: anthropic (>=0.108.0) ; extra == "anthropic"
26
26
  Requires-Dist: claude-agent-sdk (>=0.1.53) ; extra == "claude"
27
- Requires-Dist: litellm (>=1.84.0)
27
+ Requires-Dist: litellm (>=1.97.0)
28
28
  Requires-Dist: openai (>=1.70.0)
29
29
  Requires-Dist: openai-agents (>=0.18.1)
30
30
  Requires-Dist: pypdfium2 (>=4.30.0)
@@ -218,7 +218,7 @@ You can customize the processing with additional optional arguments (the structu
218
218
 
219
219
  ```
220
220
  --mode Processing mode: flash (default) or standard
221
- --model LLM model to use (default: gpt-4o-2024-11-20)
221
+ --index-model LLM model used to index the document (default: gpt-5.6-luna)
222
222
  --toc-check-pages Pages to check for table of contents (default: 20)
223
223
  --max-pages-per-node Max pages per node (default: 10)
224
224
  --max-tokens-per-node Max tokens per node (default: 20000)
@@ -254,10 +254,6 @@ python3 run_pageindex.py --md_path /path/to/your/document.md
254
254
  For a simple, end-to-end **agentic vectorless RAG** example using **self-hosted PageIndex** (with OpenAI Agents SDK), see [`examples/agentic_vectorless_rag_demo.py`](examples/agentic_vectorless_rag_demo.py).
255
255
 
256
256
  ```bash
257
- # Install optional dependency
258
- pip3 install openai-agents
259
-
260
- # Run the demo
261
257
  python3 examples/agentic_vectorless_rag_demo.py
262
258
  ```
263
259
 
@@ -177,7 +177,7 @@ You can customize the processing with additional optional arguments (the structu
177
177
 
178
178
  ```
179
179
  --mode Processing mode: flash (default) or standard
180
- --model LLM model to use (default: gpt-4o-2024-11-20)
180
+ --index-model LLM model used to index the document (default: gpt-5.6-luna)
181
181
  --toc-check-pages Pages to check for table of contents (default: 20)
182
182
  --max-pages-per-node Max pages per node (default: 10)
183
183
  --max-tokens-per-node Max tokens per node (default: 20000)
@@ -213,10 +213,6 @@ python3 run_pageindex.py --md_path /path/to/your/document.md
213
213
  For a simple, end-to-end **agentic vectorless RAG** example using **self-hosted PageIndex** (with OpenAI Agents SDK), see [`examples/agentic_vectorless_rag_demo.py`](examples/agentic_vectorless_rag_demo.py).
214
214
 
215
215
  ```bash
216
- # Install optional dependency
217
- pip3 install openai-agents
218
-
219
- # Run the demo
220
216
  python3 examples/agentic_vectorless_rag_demo.py
221
217
  ```
222
218
 
@@ -1,6 +1,11 @@
1
1
  """PageIndex SDK."""
2
+ import os as _os
2
3
  from typing import TYPE_CHECKING as _TYPE_CHECKING
3
4
 
5
+ # LiteLLM's import otherwise fetches its model map over the network — seconds
6
+ # of blocking (or a hang offline). setdefault, so an explicit user choice wins.
7
+ _os.environ.setdefault("LITELLM_LOCAL_MODEL_COST_MAP", "True")
8
+
4
9
  from .client import PageIndexClient, PageIndexCloudClient, PageIndexLocalClient
5
10
  from .errors import PageIndexAPIError
6
11
 
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import os
5
+ import threading
5
6
  import time
6
7
  import warnings
7
8
  from typing import Any, Callable, Iterator, Optional, Union, cast
@@ -9,6 +10,13 @@ from typing import Any, Callable, Iterator, Optional, Union, cast
9
10
  from .errors import PageIndexAPIError
10
11
 
11
12
 
13
+ def _preload_litellm() -> None:
14
+ try:
15
+ import litellm # noqa: F401
16
+ except Exception:
17
+ pass
18
+
19
+
12
20
  def _parse_pages(pages: str) -> list[int]:
13
21
  result: set[int] = set()
14
22
  too_many = (f"Page specification '{pages}' spans more than "
@@ -33,7 +41,7 @@ def _parse_pages(pages: str) -> list[int]:
33
41
  return sorted(result)
34
42
 
35
43
 
36
- def _normalize_retrieve_model(model: str) -> str:
44
+ def _agents_sdk_model_name(model: str) -> str:
37
45
  """Preserve supported Agents SDK prefixes and route other provider paths via LiteLLM."""
38
46
  passthrough_prefixes = ("litellm/", "openai/")
39
47
  if not model or "/" not in model:
@@ -56,18 +64,38 @@ class PageIndexClient:
56
64
  Args:
57
65
  api_key (str, optional): PageIndex cloud API key
58
66
  (https://dash.pageindex.ai/api-keys). Omit for local mode.
59
- model (str, optional): Local mode only — LLM used to build document
60
- trees. Defaults to the packaged config (see pageindex/config.yaml).
61
- summary_model (str, optional): Local mode only — LLM used for node
62
- summaries and document descriptions.
63
- retrieve_model (str, optional): Local mode only — the model the
64
- local chat surfaces (``chat_completions``, ``responses``)
65
- default to, exposed as ``client.retrieve_model``.
66
- ``provider/model`` names route through LiteLLM; for an
67
- OpenAI-compatible server that itself serves slashed model ids
68
- (vLLM, TGI), prefix ``openai/`` (e.g. ``openai/Qwen/...``).
67
+ index_model (str, optional): Local mode only — LLM used to index
68
+ documents (structure and summaries). Defaults to the SDK
69
+ default (fast and cheap).
70
+ chat_model (str, optional): Local mode only — the model the chat
71
+ surfaces (``chat``, ``chat_completions``, ``responses``)
72
+ default to, exposed as ``client.chat_model``. Chat names
73
+ route through LiteLLM and mean what LiteLLM says they mean;
74
+ bare names are OpenAI-compatible shorthand, and
75
+ ``openai/Qwen/...`` is the form for an OpenAI-compatible
76
+ server that itself serves slashed model ids (vLLM, TGI).
77
+ Defaults to the SDK default (strong).
78
+ model (str, optional): Local mode only — one model for both roles:
79
+ sets the default for ``index_model`` and ``chat_model`` at
80
+ once. The role-specific arguments win over it. (Also the
81
+ 0.2.8-era name for the indexing model — old configs keep
82
+ working unchanged.)
83
+ summary_model (str, optional): Local mode only — legacy: overrides
84
+ the model used for node summaries and document descriptions;
85
+ ``index_model`` covers this.
86
+ retrieve_model (str, optional): Local mode only — legacy name for
87
+ ``chat_model``.
69
88
  storage_path (str, optional): Local mode only — directory where
70
89
  indexed documents are stored. Defaults to ``./.pageindex``.
90
+ index_backend (dict, optional): Local mode only — connection
91
+ overrides for the indexing lane's LLM calls. Keys are
92
+ LiteLLM's own connection params — ``api_key``, ``api_base``,
93
+ ``api_version``, ``aws_*``, … — passed through verbatim.
94
+ chat_backend (dict, optional): Local mode only — default
95
+ connection overrides for the chat surfaces; a call's own
96
+ ``backend`` keys win over it. The dict reaches whichever
97
+ door runs, in that door's vocabulary (see each method) —
98
+ ``api_key`` / ``base_url`` mean the same thing on all three.
71
99
 
72
100
  Usage:
73
101
  client = PageIndexClient(api_key="...") # cloud
@@ -88,19 +116,27 @@ class PageIndexClient:
88
116
  self,
89
117
  api_key: Optional[str] = None,
90
118
  *,
119
+ index_model: Optional[str] = None,
120
+ chat_model: Optional[str] = None,
91
121
  model: Optional[str] = None,
92
122
  summary_model: Optional[str] = None,
93
123
  retrieve_model: Optional[str] = None,
94
124
  storage_path: Optional[str] = None,
125
+ index_backend: Optional[dict[str, Any]] = None,
126
+ chat_backend: Optional[dict[str, Any]] = None,
95
127
  ):
96
128
  if api_key == "":
97
129
  raise PageIndexAPIError(
98
130
  "api_key is an empty string. Pass a real PageIndex API key for "
99
131
  "cloud mode, or omit api_key entirely for local mode."
100
132
  )
133
+ model_args = {"index_model": index_model, "chat_model": chat_model,
134
+ "model": model, "summary_model": summary_model,
135
+ "retrieve_model": retrieve_model}
101
136
  if api_key is not None:
102
- local_only = {"model": model, "summary_model": summary_model,
103
- "retrieve_model": retrieve_model, "storage_path": storage_path}
137
+ local_only = dict(model_args, storage_path=storage_path,
138
+ index_backend=index_backend,
139
+ chat_backend=chat_backend)
104
140
  passed = [name for name, value in local_only.items() if value is not None]
105
141
  if passed:
106
142
  raise PageIndexAPIError(
@@ -113,23 +149,31 @@ class PageIndexClient:
113
149
  self._api = CloudAPI(self)
114
150
  else:
115
151
  from .utils import ConfigLoader
116
- overrides = {key: value for key, value in
117
- {"model": model, "summary_model": summary_model,
118
- "retrieve_model": retrieve_model}.items()
152
+ overrides = {key: value for key, value in model_args.items()
119
153
  if value}
120
154
  opt = ConfigLoader().load(overrides or None)
121
155
  self.model = opt.model
122
- self.summary_model = getattr(opt, "summary_model", None) or opt.model
123
- self.retrieve_model = _normalize_retrieve_model(
124
- getattr(opt, "retrieve_model", None) or opt.model)
156
+ self.index_model = opt.index_model
157
+ self.summary_model = opt.summary_model
158
+ self.chat_model = _agents_sdk_model_name(opt.chat_model)
159
+ self.chat_backend = chat_backend
125
160
  self.storage_path = storage_path or ".pageindex"
126
161
  from .local_api import LocalAPI
127
162
  self._api = LocalAPI(
128
163
  storage_path=self.storage_path,
129
164
  model=self.model,
130
165
  summary_model=self.summary_model,
131
- retrieve_model=self.retrieve_model,
166
+ retrieve_model=self.chat_model,
167
+ index_backend=index_backend,
132
168
  )
169
+ # LiteLLM's multi-second import would otherwise land on the
170
+ # first chat call; failures resurface there with real context.
171
+ threading.Thread(target=_preload_litellm, daemon=True).start()
172
+
173
+ @property
174
+ def retrieve_model(self):
175
+ """Legacy name for ``chat_model``."""
176
+ return self.chat_model
133
177
 
134
178
  # ---------- DOCUMENT SUBMISSION ----------
135
179
 
@@ -353,6 +397,7 @@ class PageIndexClient:
353
397
  doc_id: Optional[Union[str, list[str]]] = None,
354
398
  stream: bool = False,
355
399
  model: Optional[str] = None,
400
+ reasoning_effort: Optional[str] = None,
356
401
  ) -> Union[str, Iterator[str]]:
357
402
  """
358
403
  Ask a question about your documents, get the answer.
@@ -371,14 +416,19 @@ class PageIndexClient:
371
416
  Keep it identical across a conversation's calls.
372
417
  stream: Yield the answer as text chunks as it is produced.
373
418
  model: Local only — backend model name (defaults to
374
- ``retrieve_model``).
419
+ ``chat_model``).
420
+ reasoning_effort: Local only — how hard the model thinks
421
+ (``"low"`` / ``"medium"`` / ``"high"``; what a backend
422
+ accepts is its own). Unset sends nothing — the model's
423
+ default behavior applies.
375
424
 
376
425
  Returns:
377
426
  - stream=False: the answer string
378
427
  - stream=True: iterator of text chunks
379
428
  """
380
429
  result = self.chat_completions(messages, stream=stream,
381
- doc_id=doc_id, model=model)
430
+ doc_id=doc_id, model=model,
431
+ reasoning_effort=reasoning_effort)
382
432
  if stream:
383
433
  return cast(Iterator[str], result)
384
434
  envelope = cast(dict[str, Any], result)
@@ -394,20 +444,26 @@ class PageIndexClient:
394
444
  enable_citations: bool = False,
395
445
  model: Optional[str] = None,
396
446
  max_turns: Optional[int] = None,
447
+ top_p: Optional[float] = None,
448
+ max_tokens: Optional[int] = None,
449
+ reasoning_effort: Optional[str] = None,
450
+ extra_body: Optional[dict[str, Any]] = None,
451
+ extra_headers: Optional[dict[str, str]] = None,
452
+ backend: Optional[dict[str, Any]] = None,
397
453
  ) -> Union[dict[str, Any], Iterator[str], Iterator[dict[str, Any]]]:
398
454
  """
399
455
  PageIndex Chat Completions: document QA in one call.
400
456
 
401
457
  Cloud: the hosted chat endpoint. Local: a managed document-QA agent
402
- run over the local tools against your own LLM backend's
403
- /chat/completions (the OpenAI SDK's
404
- usual env config — OPENAI_API_KEY, OPENAI_BASE_URL — selects the
405
- backend, so any OpenAI-compatible server works; a ``/`` in the
406
- model name means LiteLLM provider routing, so prefix ``openai/``
407
- when the backend itself serves slashed ids, e.g.
408
- ``openai/Qwen/...`` on vLLM; LiteLLM-routed Claude models —
409
- Anthropic direct, Bedrock, Vertex — get the managed prompt
410
- prefix cache-marked automatically). The non-stream
458
+ run over the local tools against your own LLM backend, routed
459
+ through LiteLLM — model names mean what LiteLLM says they mean.
460
+ Bare names are OpenAI-compatible shorthand (the OpenAI SDK's usual
461
+ env config — OPENAI_API_KEY, OPENAI_BASE_URL — selects the
462
+ backend, so any OpenAI-compatible server works; write
463
+ ``openai/Qwen/...`` when the server itself serves slashed ids),
464
+ provider-prefixed names — ``anthropic/…``, ``bedrock/…`` — reach
465
+ that provider, and LiteLLM-routed Claude models get the managed
466
+ prompt prefix cache-marked automatically. The non-stream
411
467
  response carries the final answer only; streaming yields the
412
468
  agent's visible text as it is produced, including narration before
413
469
  tool calls. ``finish_reason`` reports loop completion ("stop") —
@@ -434,8 +490,34 @@ class PageIndexClient:
434
490
  enable_citations: Cloud-only — local mode raises (citations need
435
491
  block-level OCR data local mode does not store).
436
492
  model: Local only — backend model name (defaults to
437
- ``retrieve_model``). The cloud endpoint selects its own.
493
+ ``chat_model``). The cloud endpoint selects its own.
438
494
  max_turns: Local only — cap on agent turns per call.
495
+ top_p: Local only — nucleus sampling, passed through to the
496
+ model.
497
+ max_tokens: Local only — per-call output cap, passed through;
498
+ it bounds each backend call in the agent loop (the way
499
+ max_turns bounds the loop), not the whole run.
500
+ reasoning_effort: Local only — passed through verbatim as
501
+ LiteLLM's ``reasoning_effort``; each provider maps it to
502
+ its own thinking control, and the values mean what the
503
+ backend says they mean. Unset sends nothing (the
504
+ backend's default applies).
505
+ extra_body: Local only — extra request fields beyond this
506
+ method's parameters, merged last so they win.
507
+ OpenAI-compatible backends take them verbatim in the
508
+ request body; LiteLLM-routed providers take them as
509
+ LiteLLM's own params (mapped or refused per provider).
510
+ Credentials belong in ``backend``, never here.
511
+ extra_headers: Local only — extra HTTP headers merged into
512
+ each backend request; caller headers win. One exception:
513
+ LiteLLM's anthropic adapter owns the ``anthropic-beta``
514
+ header (your value is dropped there) — use ``messages()``
515
+ for Anthropic beta flags.
516
+ backend: Local only — connection overrides for this call's
517
+ backend, merged over the client's ``chat_backend``
518
+ (per-call keys win). Keys are LiteLLM's own connection
519
+ params — ``api_key``, ``base_url``, ``api_version``,
520
+ ``aws_*``, … — passed through verbatim.
439
521
 
440
522
  Returns:
441
523
  - stream=False: complete response dict ({'id', 'object', 'created',
@@ -456,12 +538,18 @@ class PageIndexClient:
456
538
  self, messages, stream=stream, doc_id=doc_id,
457
539
  temperature=temperature, stream_metadata=stream_metadata,
458
540
  enable_citations=enable_citations, model=model,
459
- max_turns=max_turns,
541
+ max_turns=max_turns, top_p=top_p, max_tokens=max_tokens,
542
+ reasoning_effort=reasoning_effort, extra_body=extra_body,
543
+ extra_headers=extra_headers, backend=backend,
460
544
  )
461
- if model is not None or max_turns is not None:
545
+ if (model is not None or max_turns is not None or top_p is not None
546
+ or max_tokens is not None or reasoning_effort is not None
547
+ or extra_body is not None or extra_headers is not None
548
+ or backend is not None):
462
549
  raise PageIndexAPIError(
463
- "model and max_turns are local-mode parameters — the cloud "
464
- "chat endpoint selects its own model."
550
+ "model, max_turns, top_p, max_tokens, reasoning_effort, "
551
+ "extra_body, extra_headers and backend are local-mode "
552
+ "parameters — the cloud chat endpoint selects its own model."
465
553
  )
466
554
  return self._api.chat_completions(
467
555
  messages=messages, stream=stream, doc_id=doc_id,
@@ -479,6 +567,11 @@ class PageIndexClient:
479
567
  temperature: Optional[float] = None,
480
568
  top_p: Optional[float] = None,
481
569
  max_turns: Optional[int] = None,
570
+ max_output_tokens: Optional[int] = None,
571
+ reasoning: Optional[dict[str, Any]] = None,
572
+ extra_body: Optional[dict[str, Any]] = None,
573
+ extra_headers: Optional[dict[str, str]] = None,
574
+ backend: Optional[dict[str, Any]] = None,
482
575
  ) -> Union[dict[str, Any], Iterator[dict[str, Any]]]:
483
576
  """
484
577
  Document QA over the OpenAI Responses protocol — the agentic surface.
@@ -502,7 +595,7 @@ class PageIndexClient:
502
595
  Args:
503
596
  input: A user message string, or a list of Responses input items
504
597
  (round-trip prior ``items`` here).
505
- model: Backend model name (defaults to ``retrieve_model``).
598
+ model: Backend model name (defaults to ``chat_model``).
506
599
  stream: Yield Responses stream events as dicts — one logical
507
600
  response per call: per-turn backend lifecycle events are
508
601
  collapsed, sequence numbers are reassigned monotonically,
@@ -517,6 +610,25 @@ class PageIndexClient:
517
610
  instructions: Appended to the managed system prompt.
518
611
  temperature / top_p: Passed through to the model.
519
612
  max_turns: Cap on agent turns per call.
613
+ max_output_tokens: Per-call output cap, passed through; it
614
+ bounds each backend call in the agent loop (the way
615
+ max_turns bounds the loop), not the whole run. Echoed in
616
+ the envelope.
617
+ reasoning: Responses reasoning options, forwarded verbatim
618
+ (e.g. ``{"effort": "low", "summary": "auto"}``) — the
619
+ values mean what the backend says they mean. Unset sends
620
+ nothing (the backend's default applies).
621
+ extra_body: Extra request fields beyond this method's
622
+ parameters, merged verbatim into the request body (last,
623
+ so they win). Credentials belong in ``backend``, never
624
+ here.
625
+ extra_headers: Extra HTTP headers merged into each request;
626
+ caller headers win over defaults.
627
+ backend: Connection overrides for this call's backend client,
628
+ merged over the client's ``chat_backend`` (per-call keys
629
+ win). Keys are the openai SDK's client params —
630
+ ``api_key``, ``base_url``, ``organization``, … — passed
631
+ verbatim; unknown keys raise.
520
632
  """
521
633
  from .cloud_api import CloudAPI
522
634
  if isinstance(self._api, CloudAPI):
@@ -528,7 +640,9 @@ class PageIndexClient:
528
640
  return run_responses(
529
641
  self, input, model=model, stream=stream, doc_id=doc_id,
530
642
  instructions=instructions, temperature=temperature, top_p=top_p,
531
- max_turns=max_turns,
643
+ max_turns=max_turns, max_output_tokens=max_output_tokens,
644
+ reasoning=reasoning, extra_body=extra_body,
645
+ extra_headers=extra_headers, backend=backend,
532
646
  )
533
647
 
534
648
  def messages(
@@ -544,6 +658,10 @@ class PageIndexClient:
544
658
  top_k: Optional[int] = None,
545
659
  stop_sequences: Optional[list[str]] = None,
546
660
  max_turns: Optional[int] = None,
661
+ thinking: Optional[dict[str, Any]] = None,
662
+ extra_body: Optional[dict[str, Any]] = None,
663
+ extra_headers: Optional[dict[str, str]] = None,
664
+ backend: Optional[dict[str, Any]] = None,
547
665
  ) -> Union[dict[str, Any], Iterator[Any]]:
548
666
  """
549
667
  Document QA over the Anthropic Messages protocol — Claude-native.
@@ -578,6 +696,20 @@ class PageIndexClient:
578
696
  OpenAI surfaces). A truncated run reports
579
697
  ``stop_reason: "tool_use"`` and its ``messages`` remain
580
698
  valid for continuation.
699
+ thinking: Anthropic thinking configuration, forwarded verbatim
700
+ (e.g. ``{"type": "adaptive"}``) — the values and their
701
+ constraints are the backend's. Unset sends nothing.
702
+ extra_body: Extra request fields beyond this method's
703
+ parameters, merged verbatim into each request body.
704
+ Credentials belong in ``backend``, never here.
705
+ extra_headers: Extra HTTP headers merged into each request
706
+ (e.g. ``anthropic-beta`` feature flags); caller headers
707
+ win over defaults.
708
+ backend: Connection overrides for this call's backend client,
709
+ merged over the client's ``chat_backend`` (per-call keys
710
+ win). Keys are the anthropic SDK's client params —
711
+ ``api_key``, ``base_url``, ``auth_token``, … — passed
712
+ verbatim; unknown keys raise.
581
713
  """
582
714
  from .cloud_api import CloudAPI
583
715
  if isinstance(self._api, CloudAPI):
@@ -591,6 +723,8 @@ class PageIndexClient:
591
723
  stream=stream, doc_id=doc_id, system=system,
592
724
  temperature=temperature, top_p=top_p, top_k=top_k,
593
725
  stop_sequences=stop_sequences, max_turns=max_turns,
726
+ thinking=thinking, extra_body=extra_body,
727
+ extra_headers=extra_headers, backend=backend,
594
728
  )
595
729
 
596
730
  # ---------- DOCUMENT MANAGEMENT ----------
@@ -742,9 +876,11 @@ class PageIndexClient:
742
876
  Sugar over the explicit form — ``agent_instructions`` (with
743
877
  ``doc_id`` targeting) as the instructions and
744
878
  ``as_openai_tools`` as the tools; local clients also carry their
745
- configured ``retrieve_model`` (cloud omits ``model`` so the
879
+ configured ``chat_model`` (cloud omits ``model`` so the
746
880
  framework default applies). To customize further, switch to
747
- those methods directly.
881
+ those methods directly. You run this config in your own
882
+ environment, so its model auth comes from there —
883
+ ``chat_backend`` does not travel with it.
748
884
 
749
885
  Args:
750
886
  doc_id: Document ID or list of IDs to target, as in
@@ -763,7 +899,7 @@ class PageIndexClient:
763
899
  scoped=scope is not None),
764
900
  "tools": self.as_openai_tools(include_management, doc_id=scope),
765
901
  }
766
- model = model or getattr(self, "retrieve_model", None)
902
+ model = model or getattr(self, "chat_model", None)
767
903
  if model:
768
904
  config["model"] = model
769
905
  return config
@@ -1020,10 +1156,16 @@ class PageIndexLocalClient(PageIndexClient):
1020
1156
  def __init__(
1021
1157
  self,
1022
1158
  *,
1159
+ index_model: Optional[str] = None,
1160
+ chat_model: Optional[str] = None,
1023
1161
  model: Optional[str] = None,
1024
1162
  summary_model: Optional[str] = None,
1025
1163
  retrieve_model: Optional[str] = None,
1026
1164
  storage_path: Optional[str] = None,
1165
+ index_backend: Optional[dict[str, Any]] = None,
1166
+ chat_backend: Optional[dict[str, Any]] = None,
1027
1167
  ):
1028
- super().__init__(None, model=model, summary_model=summary_model,
1029
- retrieve_model=retrieve_model, storage_path=storage_path)
1168
+ super().__init__(None, index_model=index_model, chat_model=chat_model,
1169
+ model=model, summary_model=summary_model,
1170
+ retrieve_model=retrieve_model, storage_path=storage_path,
1171
+ index_backend=index_backend, chat_backend=chat_backend)
@@ -0,0 +1,15 @@
1
+ # Models — index_model indexes documents (structure and summaries),
2
+ # chat_model answers questions on the chat surfaces; set model to use one
3
+ # for both. Unset keys use the SDK defaults shown below. Legacy keys
4
+ # (model, summary_model, retrieve_model) keep working.
5
+ # Indexing names without a provider prefix use the OpenAI SDK directly;
6
+ # for other providers, use "provider/model" (e.g. "anthropic/claude-sonnet-4-6").
7
+ # index_model: "gpt-5.6-luna"
8
+ # chat_model: "gpt-5.6-sol"
9
+ toc_check_page_num: 20
10
+ max_page_num_each_node: 10
11
+ max_token_num_each_node: 20000
12
+ if_add_node_id: "yes"
13
+ if_add_node_summary: "yes"
14
+ if_add_doc_description: "no"
15
+ if_add_node_text: "no"
@@ -35,14 +35,25 @@ class LocalAPI:
35
35
  """Backs PageIndexClient's local mode. One instance per client."""
36
36
 
37
37
  def __init__(self, storage_path: str, model: str, summary_model: str,
38
- retrieve_model: str):
38
+ retrieve_model: str, index_backend: dict | None = None):
39
39
  self._store = DocStore(storage_path)
40
40
  self._model = model
41
41
  self._summary_model = summary_model
42
42
  self._retrieve_model = retrieve_model
43
+ self._index_backend = index_backend
43
44
  from .utils import ConfigLoader
44
45
  self._config_loader = ConfigLoader()
45
46
 
47
+ def _with_backend(self, func, *args):
48
+ """Scope the indexing lane's connection overrides around one
49
+ operation — runs inside whatever thread _run_indexer picked."""
50
+ from .utils import _llm_backend
51
+ token = _llm_backend.set(self._index_backend)
52
+ try:
53
+ return func(*args)
54
+ finally:
55
+ _llm_backend.reset(token)
56
+
46
57
  # ── indexing ──
47
58
 
48
59
  def submit_document(
@@ -104,11 +115,12 @@ class LocalAPI:
104
115
  try:
105
116
  if mode == "flash":
106
117
  structure, description = _run_indexer(
107
- self._index_flash, file_path, page_texts
118
+ self._with_backend, self._index_flash, file_path, page_texts
108
119
  )
109
120
  else:
110
121
  structure, description = _run_indexer(
111
- self._index_standard, file_path, page_texts
122
+ self._with_backend, self._index_standard, file_path,
123
+ page_texts
112
124
  )
113
125
  except PageIndexAPIError:
114
126
  raise
@@ -183,11 +195,12 @@ class LocalAPI:
183
195
  from .utils import (add_node_text, create_clean_structure_for_description,
184
196
  generate_doc_description, write_node_id)
185
197
  import litellm
186
- env = litellm.validate_environment(self._summary_model)
187
- if not env["keys_in_environment"]:
188
- raise PageIndexAPIError(
189
- f"Failed to submit document: missing API key for "
190
- f"{self._summary_model}: {', '.join(env['missing_keys'])}")
198
+ if not self._index_backend:
199
+ env = litellm.validate_environment(self._summary_model)
200
+ if not env["keys_in_environment"]:
201
+ raise PageIndexAPIError(
202
+ f"Failed to submit document: missing API key for "
203
+ f"{self._summary_model}: {', '.join(env['missing_keys'])}")
191
204
  result = page_index_flash(file_path, summary=True,
192
205
  summary_model=self._summary_model,
193
206
  optimize="full",