pageindex 0.2.10.dev4__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 (103) hide show
  1. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/PKG-INFO +1 -1
  2. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/client.py +66 -8
  3. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/local_api.py +21 -8
  4. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/local_chat.py +54 -17
  5. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/page_index_classic.py +0 -1
  6. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/utils.py +39 -10
  7. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pyproject.toml +1 -1
  8. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/LICENSE +0 -0
  9. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/README.md +0 -0
  10. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/__init__.py +0 -0
  11. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/_version.py +0 -0
  12. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/agent_tools.py +0 -0
  13. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/cloud_api.py +0 -0
  14. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/config.yaml +0 -0
  15. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/errors.py +0 -0
  16. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/README.md +0 -0
  17. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/__init__.py +0 -0
  18. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/api.py +0 -0
  19. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/blocks/__init__.py +0 -0
  20. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/blocks/build.py +0 -0
  21. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/blocks/join_rules.py +0 -0
  22. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/classification/__init__.py +0 -0
  23. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/classification/body_text.py +0 -0
  24. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/classification/header_footer.py +0 -0
  25. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/classification/keyword_tables.py +0 -0
  26. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/classification/toc_boilerplate.py +0 -0
  27. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/clustering/__init__.py +0 -0
  28. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/clustering/build.py +0 -0
  29. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/clustering/merge_rules.py +0 -0
  30. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/columns/__init__.py +0 -0
  31. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/columns/gutters.py +0 -0
  32. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/columns/splitting.py +0 -0
  33. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/data/boilerplate_phrases.json +0 -0
  34. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/data/dictionaries.json +0 -0
  35. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/data/glyph_name_table.json +0 -0
  36. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/data/normalized_unicodes.json +0 -0
  37. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/data/script_bucket_table.json +0 -0
  38. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/embedded_toc.py +0 -0
  39. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/__init__.py +0 -0
  40. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/candidates.py +0 -0
  41. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/detectors.py +0 -0
  42. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/keyword_tables.py +0 -0
  43. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/neighbors.py +0 -0
  44. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/page_scan.py +0 -0
  45. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/style_detectors.py +0 -0
  46. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/heading_detection/text_checks.py +0 -0
  47. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/labels/__init__.py +0 -0
  48. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/labels/caption_regions.py +0 -0
  49. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/labels/caption_text.py +0 -0
  50. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/main.py +0 -0
  51. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/model/__init__.py +0 -0
  52. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/model/block.py +0 -0
  53. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/model/char_stats.py +0 -0
  54. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/model/numbering.py +0 -0
  55. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/model/rects.py +0 -0
  56. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/model/span_line.py +0 -0
  57. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline/__init__.py +0 -0
  58. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline/filtering.py +0 -0
  59. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline/tree.py +0 -0
  60. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/__init__.py +0 -0
  61. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/assembly.py +0 -0
  62. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/candidates.py +0 -0
  63. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/cliques.py +0 -0
  64. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/selection.py +0 -0
  65. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/outline_assembly/style_context.py +0 -0
  66. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/__init__.py +0 -0
  67. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/char_extract.py +0 -0
  68. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/cmap_parse.py +0 -0
  69. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/code_walk.py +0 -0
  70. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/content_stream.py +0 -0
  71. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/font_unicode.py +0 -0
  72. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/geometry.py +0 -0
  73. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/glyph_tables.py +0 -0
  74. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/merge.py +0 -0
  75. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/pdf_objects.py +0 -0
  76. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/pipeline.py +0 -0
  77. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/remerge.py +0 -0
  78. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/text_normalize.py +0 -0
  79. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_charlevel/unicode_apply.py +0 -0
  80. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/parser_pdfium_parallel.py +0 -0
  81. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/phases/__init__.py +0 -0
  82. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/phases/line_numbers.py +0 -0
  83. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/phases/page_view.py +0 -0
  84. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/stats/__init__.py +0 -0
  85. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/stats/aggregates.py +0 -0
  86. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/stats/scripts.py +0 -0
  87. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/title/__init__.py +0 -0
  88. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/title/detect.py +0 -0
  89. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/title/dicts.py +0 -0
  90. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/title/scoring.py +0 -0
  91. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/__init__.py +0 -0
  92. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/hashing.py +0 -0
  93. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/token_types.py +0 -0
  94. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/tokenizer.py +0 -0
  95. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/flash/tokens/tries.py +0 -0
  96. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/integrations/__init__.py +0 -0
  97. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/integrations/anthropic_sdk.py +0 -0
  98. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/integrations/claude_agent_sdk.py +0 -0
  99. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/integrations/openai_agents.py +0 -0
  100. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/local_store.py +0 -0
  101. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/mcp_bridge.py +0 -0
  102. {pageindex-0.2.10.dev4 → pageindex-0.2.10.dev5}/pageindex/page_index_md.py +0 -0
  103. {pageindex-0.2.10.dev4 → 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.dev4
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
@@ -87,6 +87,15 @@ class PageIndexClient:
87
87
  ``chat_model``.
88
88
  storage_path (str, optional): Local mode only — directory where
89
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.
90
99
 
91
100
  Usage:
92
101
  client = PageIndexClient(api_key="...") # cloud
@@ -113,6 +122,8 @@ class PageIndexClient:
113
122
  summary_model: Optional[str] = None,
114
123
  retrieve_model: Optional[str] = None,
115
124
  storage_path: Optional[str] = None,
125
+ index_backend: Optional[dict[str, Any]] = None,
126
+ chat_backend: Optional[dict[str, Any]] = None,
116
127
  ):
117
128
  if api_key == "":
118
129
  raise PageIndexAPIError(
@@ -123,7 +134,9 @@ class PageIndexClient:
123
134
  "model": model, "summary_model": summary_model,
124
135
  "retrieve_model": retrieve_model}
125
136
  if api_key is not None:
126
- local_only = dict(model_args, storage_path=storage_path)
137
+ local_only = dict(model_args, storage_path=storage_path,
138
+ index_backend=index_backend,
139
+ chat_backend=chat_backend)
127
140
  passed = [name for name, value in local_only.items() if value is not None]
128
141
  if passed:
129
142
  raise PageIndexAPIError(
@@ -143,6 +156,7 @@ class PageIndexClient:
143
156
  self.index_model = opt.index_model
144
157
  self.summary_model = opt.summary_model
145
158
  self.chat_model = _agents_sdk_model_name(opt.chat_model)
159
+ self.chat_backend = chat_backend
146
160
  self.storage_path = storage_path or ".pageindex"
147
161
  from .local_api import LocalAPI
148
162
  self._api = LocalAPI(
@@ -150,6 +164,7 @@ class PageIndexClient:
150
164
  model=self.model,
151
165
  summary_model=self.summary_model,
152
166
  retrieve_model=self.chat_model,
167
+ index_backend=index_backend,
153
168
  )
154
169
  # LiteLLM's multi-second import would otherwise land on the
155
170
  # first chat call; failures resurface there with real context.
@@ -433,6 +448,8 @@ class PageIndexClient:
433
448
  max_tokens: Optional[int] = None,
434
449
  reasoning_effort: Optional[str] = None,
435
450
  extra_body: Optional[dict[str, Any]] = None,
451
+ extra_headers: Optional[dict[str, str]] = None,
452
+ backend: Optional[dict[str, Any]] = None,
436
453
  ) -> Union[dict[str, Any], Iterator[str], Iterator[dict[str, Any]]]:
437
454
  """
438
455
  PageIndex Chat Completions: document QA in one call.
@@ -490,6 +507,17 @@ class PageIndexClient:
490
507
  OpenAI-compatible backends take them verbatim in the
491
508
  request body; LiteLLM-routed providers take them as
492
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.
493
521
 
494
522
  Returns:
495
523
  - stream=False: complete response dict ({'id', 'object', 'created',
@@ -512,14 +540,16 @@ class PageIndexClient:
512
540
  enable_citations=enable_citations, model=model,
513
541
  max_turns=max_turns, top_p=top_p, max_tokens=max_tokens,
514
542
  reasoning_effort=reasoning_effort, extra_body=extra_body,
543
+ extra_headers=extra_headers, backend=backend,
515
544
  )
516
545
  if (model is not None or max_turns is not None or top_p is not None
517
546
  or max_tokens is not None or reasoning_effort is not None
518
- or extra_body is not None):
547
+ or extra_body is not None or extra_headers is not None
548
+ or backend is not None):
519
549
  raise PageIndexAPIError(
520
- "model, max_turns, top_p, max_tokens, reasoning_effort and "
521
- "extra_body are local-mode parameters — the cloud chat "
522
- "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."
523
553
  )
524
554
  return self._api.chat_completions(
525
555
  messages=messages, stream=stream, doc_id=doc_id,
@@ -540,6 +570,8 @@ class PageIndexClient:
540
570
  max_output_tokens: Optional[int] = None,
541
571
  reasoning: Optional[dict[str, Any]] = None,
542
572
  extra_body: Optional[dict[str, Any]] = None,
573
+ extra_headers: Optional[dict[str, str]] = None,
574
+ backend: Optional[dict[str, Any]] = None,
543
575
  ) -> Union[dict[str, Any], Iterator[dict[str, Any]]]:
544
576
  """
545
577
  Document QA over the OpenAI Responses protocol — the agentic surface.
@@ -588,7 +620,15 @@ class PageIndexClient:
588
620
  nothing (the backend's default applies).
589
621
  extra_body: Extra request fields beyond this method's
590
622
  parameters, merged verbatim into the request body (last,
591
- so they win).
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.
592
632
  """
593
633
  from .cloud_api import CloudAPI
594
634
  if isinstance(self._api, CloudAPI):
@@ -602,6 +642,7 @@ class PageIndexClient:
602
642
  instructions=instructions, temperature=temperature, top_p=top_p,
603
643
  max_turns=max_turns, max_output_tokens=max_output_tokens,
604
644
  reasoning=reasoning, extra_body=extra_body,
645
+ extra_headers=extra_headers, backend=backend,
605
646
  )
606
647
 
607
648
  def messages(
@@ -619,6 +660,8 @@ class PageIndexClient:
619
660
  max_turns: Optional[int] = None,
620
661
  thinking: Optional[dict[str, Any]] = None,
621
662
  extra_body: Optional[dict[str, Any]] = None,
663
+ extra_headers: Optional[dict[str, str]] = None,
664
+ backend: Optional[dict[str, Any]] = None,
622
665
  ) -> Union[dict[str, Any], Iterator[Any]]:
623
666
  """
624
667
  Document QA over the Anthropic Messages protocol — Claude-native.
@@ -658,6 +701,15 @@ class PageIndexClient:
658
701
  constraints are the backend's. Unset sends nothing.
659
702
  extra_body: Extra request fields beyond this method's
660
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.
661
713
  """
662
714
  from .cloud_api import CloudAPI
663
715
  if isinstance(self._api, CloudAPI):
@@ -672,6 +724,7 @@ class PageIndexClient:
672
724
  temperature=temperature, top_p=top_p, top_k=top_k,
673
725
  stop_sequences=stop_sequences, max_turns=max_turns,
674
726
  thinking=thinking, extra_body=extra_body,
727
+ extra_headers=extra_headers, backend=backend,
675
728
  )
676
729
 
677
730
  # ---------- DOCUMENT MANAGEMENT ----------
@@ -825,7 +878,9 @@ class PageIndexClient:
825
878
  ``as_openai_tools`` as the tools; local clients also carry their
826
879
  configured ``chat_model`` (cloud omits ``model`` so the
827
880
  framework default applies). To customize further, switch to
828
- 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.
829
884
 
830
885
  Args:
831
886
  doc_id: Document ID or list of IDs to target, as in
@@ -1107,7 +1162,10 @@ class PageIndexLocalClient(PageIndexClient):
1107
1162
  summary_model: Optional[str] = None,
1108
1163
  retrieve_model: Optional[str] = None,
1109
1164
  storage_path: Optional[str] = None,
1165
+ index_backend: Optional[dict[str, Any]] = None,
1166
+ chat_backend: Optional[dict[str, Any]] = None,
1110
1167
  ):
1111
1168
  super().__init__(None, index_model=index_model, chat_model=chat_model,
1112
1169
  model=model, summary_model=summary_model,
1113
- retrieve_model=retrieve_model, storage_path=storage_path)
1170
+ retrieve_model=retrieve_model, storage_path=storage_path,
1171
+ index_backend=index_backend, chat_backend=chat_backend)
@@ -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",
@@ -212,7 +212,7 @@ def _require_openai_agents(method: str) -> None:
212
212
  ) from exc
213
213
 
214
214
 
215
- def _openai_model(protocol: str, model_name: str):
215
+ def _openai_model(protocol: str, model_name: str, backend=None):
216
216
  """The backend protocol driver — the seam tests replace with a fake.
217
217
 
218
218
  chat protocol: LiteLLM, full stop — model names mean what LiteLLM says
@@ -240,12 +240,12 @@ def _openai_model(protocol: str, model_name: str):
240
240
  import openai
241
241
  model_name = model_name.removeprefix("openai/")
242
242
  try:
243
- backend = openai.AsyncOpenAI()
244
- except openai.OpenAIError as exc:
243
+ sdk_client = openai.AsyncOpenAI(**(backend or {}))
244
+ except (openai.OpenAIError, TypeError) as exc:
245
245
  raise PageIndexAPIError(
246
246
  f"The OpenAI backend is not configured: {exc}") from exc
247
247
  from agents.models.openai_responses import OpenAIResponsesModel
248
- return OpenAIResponsesModel(model_name, openai_client=backend)
248
+ return OpenAIResponsesModel(model_name, openai_client=sdk_client)
249
249
  try:
250
250
  from agents.extensions.models.litellm_model import LitellmModel
251
251
  import litellm
@@ -258,12 +258,14 @@ def _openai_model(protocol: str, model_name: str):
258
258
  _repair_litellm_types()
259
259
  wire = model_name.removeprefix("litellm/")
260
260
  if "/" not in wire or wire.startswith("openai/"):
261
- if not os.environ.get("OPENAI_API_KEY"):
261
+ if (not os.environ.get("OPENAI_API_KEY")
262
+ and not (backend or {}).get("api_key")):
262
263
  raise PageIndexAPIError(
263
264
  "The OpenAI backend is not configured: set the "
264
- "OPENAI_API_KEY environment variable (any value works "
265
- "for keyless OPENAI_BASE_URL servers), or point "
266
- "chat_model at another provider (e.g. 'anthropic/...')."
265
+ "OPENAI_API_KEY environment variable, pass an api_key "
266
+ "in chat_backend / backend (any value works for keyless "
267
+ "OPENAI_BASE_URL servers), or point chat_model at "
268
+ "another provider (e.g. 'anthropic/...')."
267
269
  )
268
270
  if "/" not in wire:
269
271
  wire = f"openai/{wire}"
@@ -276,7 +278,8 @@ def _openai_model(protocol: str, model_name: str):
276
278
  f"model id, use 'openai/{wire}' and point OPENAI_BASE_URL "
277
279
  "at the server."
278
280
  )
279
- return LitellmModel(wire)
281
+ return LitellmModel(wire, api_key=(backend or {}).get("api_key"),
282
+ base_url=(backend or {}).get("base_url"))
280
283
 
281
284
 
282
285
  def _reported_model(model_name: str) -> str:
@@ -307,10 +310,18 @@ def _cache_extra_args(model_name: str) -> Optional[dict]:
307
310
  return None
308
311
 
309
312
 
313
+ def _merged_backend(client, backend):
314
+ """This call's connection overrides: the client's ``chat_backend``
315
+ under the per-call dict, per-call keys winning."""
316
+ merged = {**(getattr(client, "chat_backend", None) or {}),
317
+ **(backend or {})}
318
+ return merged or None
319
+
320
+
310
321
  def _openai_agent(client, protocol: str, model_name: str, instructions: str,
311
322
  temperature, top_p, doc_ids=None, cache_key=None,
312
323
  reasoning=None, reasoning_effort=None, extra_body=None,
313
- max_tokens=None):
324
+ max_tokens=None, backend=None, extra_headers=None):
314
325
  from agents import Agent, ModelSettings
315
326
  from .integrations.openai_agents import build_openai_tools
316
327
  # ModelSettings.extra_body is the one channel all three engines put on
@@ -327,6 +338,16 @@ def _openai_agent(client, protocol: str, model_name: str, instructions: str,
327
338
  if reasoning_effort is not None:
328
339
  extra_args = {**(extra_args or {}),
329
340
  "reasoning_effort": reasoning_effort}
341
+ conn = dict(backend) if backend else {}
342
+ if conn and protocol == "chat":
343
+ # LiteLLM takes connection params per call, except the two names
344
+ # LitellmModel pins as its own keywords — those ride its constructor.
345
+ lifted = {"api_key": conn.pop("api_key", None),
346
+ "base_url": conn.pop("base_url", conn.pop("api_base", None))}
347
+ if conn:
348
+ extra_args = {**(extra_args or {}), **conn}
349
+ conn = {key: value for key, value in lifted.items()
350
+ if value is not None}
330
351
  body = ({"prompt_cache_key": cache_key}
331
352
  if cache_key and openai_backend else None)
332
353
  # Caller extras merge last, so they win over ours; non-OpenAI
@@ -340,11 +361,12 @@ def _openai_agent(client, protocol: str, model_name: str, instructions: str,
340
361
  name="PageIndex",
341
362
  instructions=instructions,
342
363
  tools=build_openai_tools(client, doc_ids=doc_ids),
343
- model=_openai_model(protocol, model_name),
364
+ model=_openai_model(protocol, model_name, conn or None),
344
365
  model_settings=ModelSettings(
345
366
  temperature=temperature, top_p=top_p, max_tokens=max_tokens,
346
367
  reasoning=reasoning,
347
368
  extra_body=body,
369
+ extra_headers=extra_headers,
348
370
  extra_args=extra_args),
349
371
  )
350
372
 
@@ -492,6 +514,8 @@ def run_chat_completions(client, messages, stream: bool = False,
492
514
  max_tokens: Optional[int] = None,
493
515
  reasoning_effort: Optional[str] = None,
494
516
  extra_body: Optional[dict] = None,
517
+ extra_headers: Optional[dict] = None,
518
+ backend: Optional[dict] = None,
495
519
  ) -> Union[dict, Iterator[str], Iterator[dict]]:
496
520
  if enable_citations:
497
521
  raise PageIndexAPIError(
@@ -511,7 +535,9 @@ def run_chat_completions(client, messages, stream: bool = False,
511
535
  cache_key=_conversation_cache_key(model_name,
512
536
  managed, history),
513
537
  reasoning_effort=reasoning_effort,
514
- extra_body=extra_body, max_tokens=max_tokens)
538
+ extra_body=extra_body, max_tokens=max_tokens,
539
+ backend=_merged_backend(client, backend),
540
+ extra_headers=extra_headers)
515
541
  run_kwargs = _run_kwargs(max_turns)
516
542
  import openai
517
543
  from agents import Runner
@@ -601,6 +627,8 @@ def run_responses(client, input, model: Optional[str] = None,
601
627
  max_output_tokens: Optional[int] = None,
602
628
  reasoning: Optional[dict] = None,
603
629
  extra_body: Optional[dict] = None,
630
+ extra_headers: Optional[dict] = None,
631
+ backend: Optional[dict] = None,
604
632
  ) -> Union[dict, Iterator[dict]]:
605
633
  _require_openai_agents("responses")
606
634
  _validate_max_turns(max_turns)
@@ -624,7 +652,9 @@ def run_responses(client, input, model: Optional[str] = None,
624
652
  cache_key=_conversation_cache_key(model_name, managed,
625
653
  conversation),
626
654
  reasoning=reasoning, extra_body=extra_body,
627
- max_tokens=max_output_tokens)
655
+ max_tokens=max_output_tokens,
656
+ backend=_merged_backend(client, backend),
657
+ extra_headers=extra_headers)
628
658
  run_kwargs = _run_kwargs(max_turns)
629
659
  recorded: dict = {}
630
660
  import openai
@@ -759,10 +789,14 @@ def _require_anthropic() -> None:
759
789
  ) from exc
760
790
 
761
791
 
762
- def _anthropic_client():
792
+ def _anthropic_client(backend=None):
763
793
  """The backend client — the seam tests replace with a fake transport."""
764
794
  import anthropic
765
- return anthropic.Anthropic()
795
+ try:
796
+ return anthropic.Anthropic(**(backend or {}))
797
+ except TypeError as exc:
798
+ raise PageIndexAPIError(
799
+ f"The Anthropic backend is not configured: {exc}") from exc
766
800
 
767
801
 
768
802
  def _anthropic_system(extra_system, block: Optional[str]) -> list[dict]:
@@ -837,6 +871,8 @@ def run_messages(client, messages, model: str,
837
871
  max_turns: Optional[int] = None,
838
872
  thinking: Optional[dict] = None,
839
873
  extra_body: Optional[dict] = None,
874
+ extra_headers: Optional[dict] = None,
875
+ backend: Optional[dict] = None,
840
876
  ) -> Union[dict, Iterator[Any]]:
841
877
  from .integrations.anthropic_sdk import build_anthropic_tools
842
878
 
@@ -854,9 +890,10 @@ def run_messages(client, messages, model: str,
854
890
  passthrough = {key: value for key, value in {
855
891
  "temperature": temperature, "top_p": top_p, "top_k": top_k,
856
892
  "stop_sequences": stop_sequences, "thinking": thinking,
857
- "extra_body": extra_body,
893
+ "extra_body": extra_body, "extra_headers": extra_headers,
858
894
  }.items() if value is not None}
859
- runner = _anthropic_client().beta.messages.tool_runner(
895
+ runner = _anthropic_client(_merged_backend(client, backend)) \
896
+ .beta.messages.tool_runner(
860
897
  max_tokens=(max_tokens if max_tokens is not None
861
898
  else _default_max_tokens(model)),
862
899
  messages=prepared,
@@ -7,7 +7,6 @@ import re
7
7
  from .utils import *
8
8
  from .tree_optimize import merge_tree
9
9
  import os
10
- from concurrent.futures import ThreadPoolExecutor, as_completed
11
10
 
12
11
  ######################### Hardening for prompt injection patterns ####################################################
13
12
  _INJECTION_PATTERNS = re.compile(
@@ -1,3 +1,4 @@
1
+ import contextvars
1
2
  import logging
2
3
  import os
3
4
  import sys
@@ -21,6 +22,20 @@ import re
21
22
  # and fetches a remote model-cost map.
22
23
 
23
24
 
25
+ # The indexing lane's connection overrides, scoped by LocalAPI around each
26
+ # indexing operation — a contextvar, so the value reaches this module's
27
+ # helpers and their asyncio tasks without threading it through every call.
28
+ _llm_backend: contextvars.ContextVar = contextvars.ContextVar(
29
+ "pageindex_llm_backend", default=None)
30
+
31
+
32
+ def _openai_sdk_kwargs(backend: dict) -> dict:
33
+ """The same backend dict works on both gateway paths: LiteLLM accepts
34
+ either endpoint spelling, the openai SDK only ``base_url``."""
35
+ return {("base_url" if key == "api_base" else key): value
36
+ for key, value in backend.items()}
37
+
38
+
24
39
  def _repair_litellm_types() -> None:
25
40
  """litellm 1.97.0's Message/Delta annotations carry nested forward refs
26
41
  Python 3.10 cannot resolve (BerriAI/litellm#36384), so every completion
@@ -85,15 +100,21 @@ def llm_completion(model, prompt, chat_history=None, return_finish_reason=False)
85
100
  model = _strip_prefix(model, "openai/")
86
101
  max_retries = 10
87
102
  messages = list(chat_history) + [{"role": "user", "content": prompt}] if chat_history else [{"role": "user", "content": prompt}]
103
+ backend = _llm_backend.get()
88
104
  if use_openai_sdk:
89
- global _openai_sync_client
90
- if _openai_sync_client is None:
91
- import openai
92
- _openai_sync_client = openai.OpenAI(max_retries=0)
105
+ import openai
106
+ if backend:
107
+ oai_client = openai.OpenAI(**{"max_retries": 0,
108
+ **_openai_sdk_kwargs(backend)})
109
+ else:
110
+ global _openai_sync_client
111
+ if _openai_sync_client is None:
112
+ _openai_sync_client = openai.OpenAI(max_retries=0)
113
+ oai_client = _openai_sync_client
93
114
  for i in range(max_retries):
94
115
  try:
95
116
  if use_openai_sdk:
96
- response = _openai_sync_client.chat.completions.create(
117
+ response = oai_client.chat.completions.create(
97
118
  model=model,
98
119
  messages=messages,
99
120
  )
@@ -105,6 +126,7 @@ def llm_completion(model, prompt, chat_history=None, return_finish_reason=False)
105
126
  messages=messages,
106
127
  temperature=0,
107
128
  drop_params=True,
129
+ **(backend or {}),
108
130
  )
109
131
  content = response.choices[0].message.content
110
132
  if return_finish_reason:
@@ -132,15 +154,21 @@ async def llm_acompletion(model, prompt):
132
154
  model = _strip_prefix(model, "openai/")
133
155
  max_retries = 10
134
156
  messages = [{"role": "user", "content": prompt}]
157
+ backend = _llm_backend.get()
135
158
  if use_openai_sdk:
136
- global _openai_async_client
137
- if _openai_async_client is None:
138
- import openai
139
- _openai_async_client = openai.AsyncOpenAI(max_retries=0)
159
+ import openai
160
+ if backend:
161
+ oai_client = openai.AsyncOpenAI(**{"max_retries": 0,
162
+ **_openai_sdk_kwargs(backend)})
163
+ else:
164
+ global _openai_async_client
165
+ if _openai_async_client is None:
166
+ _openai_async_client = openai.AsyncOpenAI(max_retries=0)
167
+ oai_client = _openai_async_client
140
168
  for i in range(max_retries):
141
169
  try:
142
170
  if use_openai_sdk:
143
- response = await _openai_async_client.chat.completions.create(
171
+ response = await oai_client.chat.completions.create(
144
172
  model=model,
145
173
  messages=messages,
146
174
  )
@@ -152,6 +180,7 @@ async def llm_acompletion(model, prompt):
152
180
  messages=messages,
153
181
  temperature=0,
154
182
  drop_params=True,
183
+ **(backend or {}),
155
184
  )
156
185
  return response.choices[0].message.content
157
186
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pageindex"
3
- version = "0.2.10.dev4"
3
+ version = "0.2.10.dev5"
4
4
  description = "Python SDK for PageIndex — reasoning-based, vectorless document retrieval, cloud and local"
5
5
  readme = "README.md"
6
6
  license = "MIT"
File without changes