mirror of
https://github.com/mims-harvard/ToolUniverse.git
synced 2026-09-19 07:31:47 +08:00
08246bcd4d
* docs: expand redirect map with 15 additional legacy paths Adds redirects for paths uncovered in a second-pass audit against the live published site. Each destination verified to return HTTP 200 on https://zitniklab.hms.harvard.edu/ToolUniverse/. New /tutorials/ → /guide/, /tools/, /expand_tooluniverse/ redirects: - make_your_data_searchable, make_your_data_agent_searchable, build_search_and_share_datastores → guide/make_your_data_agent_searchable - skills → guide/skills_showcase - tool_finder → guide/finding_tools - overview → guide/index - remote_tools → tools/remote_tools - mcp_integration → expand_tooluniverse/remote_tools/mcp_integration Top-level legacy pages (404 at root, now redirected): - getting_started → guide/python_guide - deployment, contributing, changelog → about/<page> - faq → help/faq Local build confirms all 22 redirect stubs generate and that every destination file exists in the build output. * docs: fix broken sphinx-tabs, dead toctrees, orphan pages, missing image Build now produces 0 warnings/errors of these structural categories (down from ~280 such issues): - Re-enable sphinx_tabs.tabs extension (3.5.0 supports Sphinx 9.x); fixes 7 broken "Unknown directive type tabs" errors that made help/faq.html and help/troubleshooting.html render incomplete. - Drop 25+ dead toctree entries in api/modules.rst and api/tooluniverse.rst that referenced per-module pages never generated by sphinx-apidoc; modules.rst now points at the existing comprehensive tooluniverse autodoc page. - Wire 16 orphan pages into the master toctree so they're reachable from the navigation, including tooluniverse_case_study, visualization_tutorial, expert_feedback, literature_search_web_ui, euhealth, logging, openrouter, streaming, vllm, wechat_community, simbad_tools, the guide/index landing page, and the full expand_tooluniverse sub-tree. - Remove three stale ":doc:" links to old/{quickstart,installation, getting_started} from sitemap.rst (those directories are excluded from the build) and mark sitemap.rst as :orphan: since it's a parallel nav surface by design. - Mark MCP_TASKS_GUIDE.md as orphan and exclude the internal DOCUMENTATION_STRUCTURE.md meta-doc from the build. - Add the missing tools/remote/ui.jpg referenced by the remote expert_feedback page (previously: broken image). * docs: eliminate all 49 structural Sphinx ERRORs (broken tables, directives, headings) Round-3 build cleanup. Builds now finish with 0 structural ERRORs of any category (previous: 49 ERRORs spread across 20 files). Total warnings/errors dropped from 280 → 114; the remaining 114 are all in Python source-file docstrings (out of scope for a docs PR). Categories fixed: **Malformed tables** (8 files) Replace ASCII grid tables with mis-aligned pipes and inline-markdown pipe tables (which RST mis-parses as substitution references) with ``list-table`` directives that render correctly across all themes: - guide/literature_search_tools_tutorial.rst (two tables) - guide/cache_system.rst (env-var table whose first column overflowed) - guide/clinical_guidelines_tools.rst - guide/make_your_data_agent_searchable.rst - expand_tooluniverse/contributing/index.rst - expand_tooluniverse/contributing/remote_tools.rst - expand_tooluniverse/reference/index.rst **list-table indentation** (3 files) Option lines and list items were indented with 1 space (only valid for 3-space) so Sphinx silently dropped them and reported "exactly one bullet list expected": - guide/finding_tools.rst - guide/http_api.rst - guide/tools.rst **Code-block separators** (5 files, ~30 directives) Add the required blank line between an introductory paragraph and a following ``.. code-block::``. Without it, Sphinx treated the directive as a continuation of the paragraph and emitted "Unexpected indentation" for every line of the code: - guide/literature_search_tools_tutorial.rst - expand_tooluniverse/quick_start.rst - expand_tooluniverse/contributing/local_tools.rst - expand_tooluniverse/contributing/remote_tools.rst - guide/make_your_data_agent_searchable.rst **Heading-style + indentation** (3 files) - guide/building_ai_scientists/mcp_name_shortening.rst — strip stray leading spaces from two section titles + downgrade unknown ``.. critical::`` to ``.. important::`` - about/deployment.rst — remove rogue ``=========`` underline below numbered-list items that mis-led the parser into skipping heading levels - guide/make_your_data_agent_searchable.rst — extend four "title underline too short" underlines + convert four markdown ``` fences to RST literal blocks **Misc directive / target fixes** - guide/python_guide.rst — replace nonexistent ``.. success::`` with a tip-styled ``.. admonition::`` - guide/euhealth_tools_tutorial.rst — indent ``.. note::`` body so it is no longer an empty admonition - help/troubleshooting.rst — same fix + remove rogue ``=========`` line that was being read as a section overline - tools/cellosaurus_tools.rst — wrap ``CVCL_`` in literal backticks so the trailing underscore stops triggering missing-target lookups - expand_tooluniverse/index.rst — promote two leading-space bullet lists to standalone lists so the indentation is correct - expand_tooluniverse/reference/architecture.rst — switch ``.. graphviz::`` (extension not installed) to a plain ``.. code-block:: text``; the embedded content was Mermaid pseudo-code anyway - guide/tools.rst — strip leading space on a section title * docs: clear all remaining content warnings (lists, headings, refs, grids) Round-4 build cleanup. Builds now finish with 0 structural ERRORs and 0 content WARNINGs; the only remaining ~11 warnings are pre-existing autodoc infrastructure noise (duplicate object index entries from autosummary, and the ghost_tool / medrxiv_tool modules that genuinely fail to import) — none are in hand-written documentation. Fixes in this commit: **sphinx-design grids** (python_guide.rst) Re-indent two ``.. grid::`` blocks whose first card + options used 1-space indentation (Sphinx silently dropped them → "parent of grid-item should be grid-row"). Also fix a ``.. button-ref::`` whose content was indented 1 space, producing a broken ``:any:`` cross-reference, and point it at the absolute ``/api/modules``. **Numbered/bulleted sub-lists** (tool_composition, literature_search ×2, architecture, literature_search_web_ui, make_your_data, agentic_tools, finding_tools, euhealth) Insert the required blank line before nested lists and re-indent 1-space sub-bullets to align under their parent list marker. Clears ~80 "list ends without a blank line; unexpected unindent" warnings. **Title underlines** (logging, tool_caller, loading_tools, tool_composition, euhealth, make_your_data, contributing/local_tools, reference/index, remote_tools/tutorial, troubleshooting + bulk pass) Extend underlines shorter than their title text; strip stray leading spaces from section titles that Sphinx read as block quotes. **Stray markdown in RST** (make_your_data, local_tools) Convert leftover ``###`` headings and ``` ``` fences to proper RST directives; remove a rogue ``------`` separator that was being parsed as a section underline. **Duplicate autosectionlabel** (make_your_data) Rename the second "How it works" heading to "How sharing works". **uniprot_tools** (JSON + generated RST) Rephrase the ``min_length`` / ``max_length`` descriptions so the open-ended range syntax no longer contains a bare ``*`` that RST read as an unterminated emphasis marker. Fixed in the JSON source so it survives doc regeneration.
225 lines
7.6 KiB
ReStructuredText
225 lines
7.6 KiB
ReStructuredText
Result Caching
|
||
==============
|
||
|
||
ToolUniverse ships with a two-tier result cache (in-memory + SQLite) so
|
||
expensive tool calls can be reused across runs without any extra setup. This
|
||
page explains what is cached, how to configure the cache, and how to inspect or
|
||
clear cached entries.
|
||
|
||
Why Use Caching?
|
||
----------------
|
||
|
||
**The Problem**: Scientific API calls can be slow and rate-limited:
|
||
|
||
- PubMed searches: 0.3-2 seconds per query
|
||
- UniProt lookups: 0.5-1 second per protein
|
||
- ChEMBL queries: 1-3 seconds per compound
|
||
- Rate limits: 3-10 requests/second (even with API keys)
|
||
|
||
**The Solution**: Cache results to avoid redundant API calls.
|
||
|
||
**Benefits**:
|
||
|
||
- **Speed**: Cached queries return in <1ms (vs 0.3-3 seconds)
|
||
- **Rate limit protection**: Avoid hitting API limits in batch workflows
|
||
- **Reproducibility**: Same query always returns same result (until source data changes)
|
||
- **Cost savings**: Fewer API calls = lower costs for paid services
|
||
- **Offline work**: Previously cached data available without internet
|
||
|
||
**Example Impact**: A 1000-protein batch analysis:
|
||
|
||
- Without cache: 1000 API calls × 0.5s = **8.3 minutes**
|
||
- With cache (90% hit rate): 100 API calls × 0.5s + 900 cached × 0.001s = **51 seconds**
|
||
- **10x faster** with caching!
|
||
|
||
Overview
|
||
--------
|
||
|
||
* **In-memory LRU** – Fastest path for repeated calls during a single session.
|
||
* **SQLite persistence** – Stores results in ``~/.tooluniverse/cache.sqlite`` by
|
||
default so results survive restarts (path configurable via environment
|
||
variables).
|
||
* **Per-tool fingerprints** – Each tool automatically fingerprints its source
|
||
code and parameter schema. When either changes, the cache key changes and old
|
||
entries are ignored.
|
||
|
||
Quick Start
|
||
-----------
|
||
|
||
Caching is enabled by default. You only need to pass ``use_cache=True`` when
|
||
calling a tool to reuse results:
|
||
|
||
.. code-block:: python
|
||
|
||
from tooluniverse import ToolUniverse
|
||
|
||
tu = ToolUniverse()
|
||
result = tu.run({
|
||
"name": "UniProt_get_entry_by_accession",
|
||
"arguments": {"accession": "P05067"},
|
||
}, use_cache=True)
|
||
|
||
# Second call reuses the cached payload
|
||
cached = tu.run({
|
||
"name": "UniProt_get_entry_by_accession",
|
||
"arguments": {"accession": "P05067"},
|
||
}, use_cache=True)
|
||
|
||
For model-training style workloads you can pass a list of calls to ``tu.run`` and
|
||
enable parallel workers. Each call still consults the cache before hitting the
|
||
network:
|
||
|
||
.. code-block:: python
|
||
|
||
import json
|
||
|
||
batch = [
|
||
{"name": "UniProt_get_entry_by_accession", "arguments": {"accession": acc}}
|
||
for acc in accession_list
|
||
]
|
||
messages = tu.run(batch, use_cache=True, max_workers=16)
|
||
|
||
# Extract tool payloads (assistant metadata is at messages[0])
|
||
payloads = [json.loads(msg["content"])["content"] for msg in messages[1:]]
|
||
|
||
Batch Execution Tips
|
||
--------------------
|
||
|
||
The batch runner performs several optimisations automatically:
|
||
|
||
* **Call deduplication** – identical ``name``/``arguments`` pairs are executed
|
||
once and fanned out to every requester. With ``use_cache=True`` the result is
|
||
also stored so later batches return instantly.
|
||
* **Per-tool concurrency** – each tool can advertise a
|
||
``batch_max_concurrency`` limit in its JSON/definition. During a batch run the
|
||
scheduler enforces that limit so that a slow or rate-limited API does not
|
||
seize all workers.
|
||
* **Configurable capacity** – increase
|
||
``TOOLUNIVERSE_CACHE_MEMORY_SIZE`` if you expect millions of cached entries.
|
||
For example, setting it to ``5000000`` keeps roughly five million results in
|
||
RAM (watch RSS usage and adjust according to payload size).
|
||
|
||
Configuration
|
||
-------------
|
||
|
||
Use environment variables to tune cache behavior before creating a
|
||
``ToolUniverse`` instance:
|
||
|
||
.. list-table::
|
||
:header-rows: 1
|
||
:widths: 40 60
|
||
|
||
* - Variable
|
||
- Description
|
||
* - ``TOOLUNIVERSE_CACHE_ENABLED``
|
||
- Turn caching on/off (``true`` by default)
|
||
* - ``TOOLUNIVERSE_CACHE_PERSIST``
|
||
- Enable SQLite persistence (``true`` by default)
|
||
* - ``TOOLUNIVERSE_CACHE_PATH``
|
||
- Full path to the SQLite file
|
||
* - ``TOOLUNIVERSE_CACHE_DIR``
|
||
- Directory for the SQLite file (default: ``~/.tooluniverse``) if ``CACHE_PATH`` unset
|
||
* - ``TOOLUNIVERSE_CACHE_MEMORY_SIZE``
|
||
- Max entries in the in-memory LRU (default 256)
|
||
* - ``TOOLUNIVERSE_CACHE_DEFAULT_TTL``
|
||
- Expiration in seconds (None disables TTL)
|
||
* - ``TOOLUNIVERSE_CACHE_SINGLEFLIGHT``
|
||
- Deduplicate concurrent misses (``true``)
|
||
* - ``TOOLUNIVERSE_CACHE_ASYNC_PERSIST``
|
||
- Write cache entries to SQLite on a background thread (``true``)
|
||
|
||
.. seealso::
|
||
:doc:`../reference/environment_variables` for complete environment variable reference including logging, LLM, and other configuration options.
|
||
|
||
Example configuration:
|
||
|
||
.. code-block:: python
|
||
|
||
import os
|
||
from tooluniverse import ToolUniverse
|
||
|
||
os.environ["TOOLUNIVERSE_CACHE_PATH"] = "/tmp/tooluniverse/cache.sqlite"
|
||
os.environ["TOOLUNIVERSE_CACHE_MEMORY_SIZE"] = "1024"
|
||
os.environ["TOOLUNIVERSE_CACHE_DEFAULT_TTL"] = "3600" # expire after 1 hour
|
||
|
||
tu = ToolUniverse()
|
||
|
||
Inspecting & Managing Cache
|
||
---------------------------
|
||
|
||
``ToolUniverse`` exposes helpers to understand and control the cache:
|
||
|
||
.. code-block:: python
|
||
|
||
stats = tu.get_cache_stats()
|
||
print(stats)
|
||
|
||
# Export persisted entries (returns an iterator of dicts)
|
||
entries = list(tu.dump_cache())
|
||
|
||
# Clear all cached data (both layers)
|
||
tu.clear_cache()
|
||
|
||
Versioning & TTL
|
||
----------------
|
||
|
||
Every tool inherits a default fingerprint (`get_cache_version`) that combines
|
||
its source code and parameter schema. You can override the hook in a custom tool
|
||
if you want finer control (for example, adding a manual ``STATIC_CACHE_VERSION``
|
||
counter). Tools can also override ``get_cache_ttl`` to specify per-result
|
||
expiration.
|
||
|
||
Asynchronous Persistence
|
||
------------------------
|
||
|
||
SQLite persistence is still enabled by default, but writes now happen on a
|
||
background thread so tool calls are not blocked on disk I/O. You can tune this
|
||
behaviour in two ways:
|
||
|
||
* Set ``TOOLUNIVERSE_CACHE_ASYNC_PERSIST=false`` before constructing
|
||
``ToolUniverse`` to fall back to fully synchronous writes (useful when you
|
||
need the result on disk immediately).
|
||
* Create a custom ``ResultCacheManager`` with a larger queue if you expect an
|
||
extremely write-heavy workload:
|
||
|
||
.. code-block:: python
|
||
|
||
from tooluniverse import ToolUniverse
|
||
from tooluniverse.cache.result_cache_manager import ResultCacheManager
|
||
|
||
tu = ToolUniverse()
|
||
tu.cache_manager.close() # replace the default manager
|
||
tu.cache_manager = ResultCacheManager(
|
||
memory_size=1_000_000,
|
||
async_queue_size=50_000,
|
||
)
|
||
|
||
Use ``tu.cache_manager.flush()`` if you need to wait for pending writes (for
|
||
example, before shutting down a worker). ``tu.get_cache_stats()`` now reports
|
||
``pending_writes`` so you can monitor the queue depth during batch jobs.
|
||
|
||
Best Practices
|
||
--------------
|
||
|
||
* Use caching for deterministic or idempotent operations (read-only API calls,
|
||
expensive computations, etc.).
|
||
* Set an explicit TTL when results are time-sensitive.
|
||
* Call ``tu.clear_cache()`` in long-running services if you need a fresh start.
|
||
* For hands-on demos, run ``examples/cache_usage_example.py`` (basic walkthrough)
|
||
or ``examples/cache_stress_test.py`` (randomized load test with summary stats).
|
||
|
||
Per-tool Concurrency
|
||
--------------------
|
||
|
||
Set ``batch_max_concurrency`` in a tool config to cap how many concurrent
|
||
executions of that tool run during batch jobs:
|
||
|
||
.. code-block:: json
|
||
|
||
{
|
||
"name": "SlowTool",
|
||
"type": "SlowTool",
|
||
"batch_max_concurrency": 2,
|
||
"parameter": {"type": "object", "properties": {}}
|
||
}
|