Skip to content

[WIP] [#521] Research Agent Tools#524

Draft
sahilds1 wants to merge 5 commits into
CodeForPhilly:developfrom
sahilds1:521-research-agent-tools
Draft

[WIP] [#521] Research Agent Tools#524
sahilds1 wants to merge 5 commits into
CodeForPhilly:developfrom
sahilds1:521-research-agent-tools

Conversation

@sahilds1

Copy link
Copy Markdown
Collaborator

Description

Related Issue

Manual Tests

Automated Tests

Documentation

Reviewers

Notes

@sahilds1 sahilds1 self-assigned this Jul 15, 2026
@sahilds1 sahilds1 changed the title Research Agent Tools [WIP] [#521] Research Agent Tools Jul 15, 2026
@sahilds1
sahilds1 marked this pull request as draft July 15, 2026 22:50
sahilds1 added 4 commits July 16, 2026 15:10
Add ask_database as a second tool for the assistant, alongside the
existing semantic search_documents tool.

- Reuse the SELECT-only, ALLOWED_TABLES-guarded ask_database
  implementation from services/tools/database.py rather than
  reimplementing the query guards in the assistant.
- Add get_tools_schema() / make_tool_mapping() as an aggregation seam
  in tool_services.py so assistant_services.py no longer names
  individual tools; new tools are registered in one place.
- Build the ask_database schema in the flattened Responses-API shape
  (not the nested Chat Completions shape from services/tools), and
  defer the database_schema_string import to call time so importing
  the module never triggers a DB query.
- Split tool_services.py: move search_documents into search_tool.py and
  the agentic-loop helpers (handle_tool_calls_with_reasoning,
  invoke_functions_from_response) into agentic_loop.py, adding the
  imports each module needs.
- Point importers (assistant_services.py, test_tool_services.py) at the
  defining module for each symbol instead of re-exporting through
  tool_services.py.
- Document the search/SQL tool overlap risk and the import-time DB
  access caveat inline.
Replace the two parallel tool registries (get_tools_schema /
make_tool_mapping) with a single Tool dataclass and a TOOLS list, so each
tool's schema and callable live under one name and can't drift apart.

- Add Tool(name, description, parameters, run) with a .schema() method;
  define SEARCH_TOOL and ASK_DATABASE_TOOL instances and a single TOOLS
  list as the source of truth. Adding a tool is appending one Tool.
- Build the ask_database schema from Medication._meta (concrete_fields /
  db_table) instead of introspecting the live database, removing the
  import-time DB query and the deferred database_schema_string import.
- Bind the request user at dispatch time: invoke_functions_from_response
  and handle_tool_calls_with_reasoning now take (tools, user), index
  tools by name, and call tool.run(user=user, **arguments). This drops
  make_tool_mapping / make_search_tool_mapping and their closures.
- assistant_services builds the schema list with
  [tool.schema() for tool in TOOLS] and forwards TOOLS + user to the loop.
- Update tests to cover the Tool instances and pass (tools, user) to the
  loop; the tool_services.search_documents / ask_database patch paths
  still resolve.
Convert every relative import in api/views/assistant to its absolute
equivalent (assistant_services, search_tool, tool_services, urls, views).
Multi-dot forms like `...services.tools.database` and `..listMeds.models`
were error-prone to read and would break silently if a file's package
depth changed; the absolute paths are move-safe and unambiguous.

Expand the tool_services.py import comments to record why search_documents
and ask_database are imported as bare names: the tests patch them at their
use site (api.views.assistant.tool_services.<name>), not their definition
site, so mock.patch rebinds the reference SEARCH_TOOL.run actually resolves
at call time. Note the maintenance guard — qualifying those calls would
move the patch target and break the tests.

Move the _medication_schema_string helper to sit directly above its only
caller, ASK_DATABASE_TOOL, instead of above SEARCH_TOOL.

No behavior change: all bound names and patch targets are preserved.
Replace the _medication_schema_string() helper (which read columns from
Medication._meta) with a hand-written _MEDICATION_SCHEMA_STRING constant,
and drop the now-unused Medication import. The _meta approach auto-synced
with the model but dumped every column and pulled in an app-registry
dependency (AppRegistryNotReady if imported during app startup). For a
4-column, stable table, a curated constant is simpler, lets us hide
columns from the LLM (omit `id`, which it never filters on), and removes
the startup coupling — at the cost of a one-line manual update if the
table's columns ever change, which the comment calls out.

Note: this drops `id` from the schema the model sees (intentional
curation, not just a port of the old behavior).

Also document in the Tool docstring why behavior is a `run` field
(composition) rather than a subclass method: the tools differ only in
which function runs, so they are instances of one concept, not distinct
types. Add a TODO listing the signals that would justify flipping to
Tool(ABC) + per-tool subclasses (per-type state, overriding more than
run, or a per-type/abstractmethod-enforced contract).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant