11from __future__ import annotations
22
33import asyncio
4+ import os
45import sys
56from contextlib import asynccontextmanager
67from pathlib import Path
1819from services .research_config_service import (
1920 config_trace_path ,
2021 load_research_config ,
22+ normalize_research_query ,
23+ research_run_kwargs ,
2124 research_tokenizer_name ,
2225)
2326from services .site_crawl_service import crawl_search
@@ -36,6 +39,10 @@ async def _ensure_local_bundle_for_config(config: dict[str, Any]) -> None:
3639 await asyncio .to_thread (ensure_onnx_bundle_sync , str (config ["embedding_model" ]))
3740
3841
42+ def _tinysearch_version () -> str :
43+ return os .environ .get ("TINYSEARCH_VERSION" , "dev" ).strip () or "dev"
44+
45+
3946@asynccontextmanager
4047async def _lifespan (_app : FastAPI ):
4148 cfg = load_research_config ()
@@ -46,7 +53,7 @@ async def _lifespan(_app: FastAPI):
4653app = FastAPI (
4754 title = "TinySearch API" ,
4855 description = "Web search, site crawl, and hybrid research endpoints." ,
49- version = "0.1.4" ,
56+ version = _tinysearch_version () ,
5057 lifespan = _lifespan ,
5158)
5259
@@ -85,6 +92,7 @@ class ResearchRequest(BaseModel):
8592 chunk_max_per_source_url : int | None = Field (default = None , ge = 0 , le = 500 )
8693 max_concurrent_crawls : int | None = Field (default = None , ge = 1 , le = 20 )
8794 max_concurrent_embedding_calls : int | None = Field (default = None , ge = 1 , le = 20 )
95+ pipeline_timeout_seconds : float | None = Field (default = None , gt = 0 )
8896 embedding_timeout_seconds : float | None = Field (default = None , gt = 0 )
8997 embedding_timeout_retries : int | None = Field (default = None , ge = 0 , le = 10 )
9098 crawl_fit_markdown_mode : str | None = None
@@ -173,94 +181,22 @@ async def site_crawl_get(
173181@app .post ("/research" )
174182async def research_endpoint (request : ResearchRequest ) -> dict [str , Any ]:
175183 config = load_research_config ()
176- embedding_model = request .embedding_model or str (config ["embedding_model" ])
184+ query = normalize_research_query (request .query )
185+ overrides = request .model_dump (exclude_none = True )
186+ overrides .pop ("query" )
187+ trace_path = overrides .pop ("trace_path" , None )
188+
189+ run_kwargs = research_run_kwargs (config )
190+ run_kwargs .update (overrides )
191+ embedding_model = str (run_kwargs ["embedding_model" ])
177192 if normalize_embedding_backend (str (config ["embedding_backend" ])) == "onnx" :
178193 from services .onnx_bundle_service import ensure_onnx_bundle_sync
179194
180195 await asyncio .to_thread (ensure_onnx_bundle_sync , embedding_model )
181196 result = await agentic_run (
182- request .query ,
183- search_top_k = request .search_top_k or int (config ["search_top_k" ]),
184- search_rrf_cutoff = request .search_rrf_cutoff
185- if request .search_rrf_cutoff is not None
186- else float (config ["search_rrf_cutoff" ]),
187- search_dense_weight = request .search_dense_weight
188- if request .search_dense_weight is not None
189- else float (config ["search_dense_weight" ]),
190- search_max_results_to_keep = request .search_max_results_to_keep
191- or int (config ["search_max_results_to_keep" ]),
192- chunk_rrf_cutoff = request .chunk_rrf_cutoff
193- if request .chunk_rrf_cutoff is not None
194- else float (config ["chunk_rrf_cutoff" ]),
195- chunk_dense_weight = request .chunk_dense_weight
196- if request .chunk_dense_weight is not None
197- else float (config ["chunk_dense_weight" ]),
198- chunk_max_results_to_keep = request .chunk_max_results_to_keep
199- or int (config ["chunk_max_results_to_keep" ]),
200- chunk_rank_oversample = request .chunk_rank_oversample
201- or int (config ["chunk_rank_oversample" ]),
202- chunk_dedupe_jaccard_threshold = request .chunk_dedupe_jaccard_threshold
203- if request .chunk_dedupe_jaccard_threshold is not None
204- else float (config ["chunk_dedupe_jaccard_threshold" ]),
205- chunk_max_per_source_url = request .chunk_max_per_source_url
206- if request .chunk_max_per_source_url is not None
207- else int (config ["chunk_max_per_source_url" ]),
208- max_concurrent_crawls = request .max_concurrent_crawls
209- or int (config ["max_concurrent_crawls" ]),
210- max_concurrent_embedding_calls = request .max_concurrent_embedding_calls
211- or int (config ["max_concurrent_embedding_calls" ]),
212- embedding_timeout_seconds = request .embedding_timeout_seconds
213- if request .embedding_timeout_seconds is not None
214- else float (config ["embedding_timeout_seconds" ]),
215- embedding_timeout_retries = request .embedding_timeout_retries
216- if request .embedding_timeout_retries is not None
217- else int (config ["embedding_timeout_retries" ]),
218- crawl_max_chunk_tokens = request .crawl_max_chunk_tokens
219- or int (config ["crawl_max_chunk_tokens" ]),
220- crawl_overlap_tokens = request .crawl_overlap_tokens
221- if request .crawl_overlap_tokens is not None
222- else int (config ["crawl_overlap_tokens" ]),
223- crawl_max_page_tokens = request .crawl_max_page_tokens
224- if request .crawl_max_page_tokens is not None
225- else int (config ["crawl_max_page_tokens" ]),
226- crawl_fit_markdown_mode = (
227- request .crawl_fit_markdown_mode
228- if request .crawl_fit_markdown_mode is not None
229- else str (config ["crawl_fit_markdown_mode" ])
230- ),
231- crawl_fit_min_chars = (
232- request .crawl_fit_min_chars
233- if request .crawl_fit_min_chars is not None
234- else int (config ["crawl_fit_min_chars" ])
235- ),
236- crawl_bm25_threshold = request .crawl_bm25_threshold
237- if request .crawl_bm25_threshold is not None
238- else float (config ["crawl_bm25_threshold" ]),
239- crawl_bm25_language = (
240- request .crawl_bm25_language
241- if request .crawl_bm25_language is not None
242- else str (config ["crawl_bm25_language" ])
243- ),
244- crawl_pruning_threshold = (
245- request .crawl_pruning_threshold
246- if request .crawl_pruning_threshold is not None
247- else float (config ["crawl_pruning_threshold" ])
248- ),
249- embedding_backend = str (config ["embedding_backend" ]),
250- embedding_model = embedding_model ,
251- embedding_openai_env_file = str (config ["embedding_openai_env_file" ]),
252- dense_query_prefix = request .dense_query_prefix
253- if request .dense_query_prefix is not None
254- else str (config ["dense_query_prefix" ]),
255- dense_document_prefix = request .dense_document_prefix
256- if request .dense_document_prefix is not None
257- else str (config ["dense_document_prefix" ]),
258- dense_document_embed_batch_size = request .dense_document_embed_batch_size
259- if request .dense_document_embed_batch_size is not None
260- else int (config ["dense_document_embed_batch_size" ]),
261- blocked_domains = config ["blocked_domains" ],
262- encoding_name = request .encoding_name or str (config ["encoding_name" ]),
263- trace_path = Path (request .trace_path ) if request .trace_path else config_trace_path (config ),
197+ query ,
198+ ** run_kwargs ,
199+ trace_path = Path (trace_path ) if trace_path else config_trace_path (config ),
264200 )
265201 return {"answer" : result .answer }
266202
0 commit comments