| # System Registry & Wiring Architecture | |
| **Status**: Active / Canonical | |
| **Last Updated**: 2025-12-03 | |
| This document serves as the **Source of Truth** for the architectural wiring of the agent framework. It defines the strict rules for decorators, protocol markers, and the tool registry to prevent regression and ensure correct system behavior. | |
| --- | |
| ## 1. Decorator Registry | |
| The agent framework relies on a strict decorator stack to inject functionality into `ChatClient` implementations. The **order of application** is critical for correct behavior. | |
| ### Standard Stack (Bottom-Up Order) | |
| | Order | Decorator | Purpose | Source | Critical Notes | | |
| |:--|:---|:---|:---|:---| | |
| | **1 (Inner)** | `@use_chat_middleware` | Handles request/response middleware processing (e.g. logging, filtering). | `agent_framework._middleware` | Must be closest to the class. | | |
| | **2** | `@use_observability` | Injects tracing and metrics (OpenTelemetry/logging). | `agent_framework.observability` | Wraps the middleware-enhanced client. | | |
| | **3 (Outer)** | `@use_function_invocation` | **CRITICAL**: Intercepts `FunctionCallContent` in responses, **executes the Python function**, and recursively calls the LLM with the result. | `agent_framework._tools` | **MUST NOT** be used if `__function_invoking_chat_client__ = True` is set (see Markers). | | |
| ### Correct Usage Example | |
| ```python | |
| @use_function_invocation # <--- 3. Handles tool execution loop | |
| @use_observability # <--- 2. Adds tracing | |
| @use_chat_middleware # <--- 1. Adds middleware support | |
| class MyChatClient(BaseChatClient): | |
| ... | |
| ``` | |
| --- | |
| ## 2. Protocol Markers | |
| Special class attributes (dunder methods/variables) that control framework behavior. | |
| | Marker | Value | Purpose | Set By | Read By | Impact of Misuse | | |
| |:---|:---|:---|:---|:---|:---| | |
| | `__function_invoking_chat_client__` | `bool` | Signals that this client **natively handles** the tool execution loop internally. | `ChatClient` Class Body | `@use_function_invocation` | **CRITICAL BUG**: If set to `True` but the client *doesn't* execute tools, tool calls will be generated by the LLM but **never executed**. The agent will hang or hallucinate results. | | |
| ### Wiring Rules | |
| * **Default Clients (OpenAI/HuggingFace):** Should generally **NOT** set this marker. Rely on `@use_function_invocation` to handle execution. | |
| * **Special Clients:** Only set to `True` if you are implementing a custom loop that executes tools and feeds results back without the framework's help. | |
| ### Setting Responsibility | |
| * **Default:** Do not set `__function_invoking_chat_client__` in the class body. The `@use_function_invocation` decorator sets it automatically after wrapping. | |
| * **Custom Loop:** Only set to `True` if you have implemented a custom tool execution loop that does not rely on the framework's decorator. | |
| --- | |
| ## 3. Tool Inventory | |
| ### 3.1 AI Functions (Agent-Callable Tools) | |
| These are the `@ai_function` decorated functions that agents can invoke. The framework executes these via `@use_function_invocation`. | |
| | Function Name | File Path | Description | | |
| |:---|:---|:---| | |
| | `search_pubmed` | `src/agents/tools.py:21` | Searches PubMed for biomedical literature | | |
| | `search_clinical_trials` | `src/agents/tools.py:81` | Searches ClinicalTrials.gov for clinical studies | | |
| | `search_preprints` | `src/agents/tools.py:121` | Searches Europe PMC for preprints and papers | | |
| | `get_bibliography` | `src/agents/tools.py:161` | Returns collected references for final report | | |
| | `execute_python_code` | `src/agents/code_executor_agent.py:16` | Executes Python code in Modal sandbox | | |
| | `search_web` | `src/agents/retrieval_agent.py:17` | Searches the web for additional context | | |
| ### 3.2 Tool Classes (Internal Wrappers) | |
| These are **internal implementation wrappers** used by the AI Functions. They are NOT directly callable by agents. | |
| | Class | File Path | Used By | | |
| |:---|:---|:---| | |
| | `PubMedTool` | `src/tools/pubmed.py` | `search_pubmed` | | |
| | `ClinicalTrialsTool` | `src/tools/clinicaltrials.py` | `search_clinical_trials` | | |
| | `EuropePMCTool` | `src/tools/europepmc.py` | `search_preprints` | | |
| | `ModalCodeExecutor` | `src/tools/code_execution.py:44` | `execute_python_code` (via `get_code_executor()`) | | |
| | `OpenAlexTool` | `src/tools/openalex.py` | (Reserved for future use) | | |
| | `WebSearchTool` | `src/tools/web_search.py` | `search_web` | | |
| | `SearchHandler` | `src/tools/search_handler.py` | Orchestrates parallel searches | | |
| --- | |
| ## 4. Client Implementation Guide | |
| When adding a new LLM provider, follow this strict pattern: | |
| ### A. The "Native Execution" Fallacy | |
| Do not assume that because an API supports "function calling" (parsing JSON), the client supports "function execution" (running Python code). | |
| * **Function Calling:** LLM -> JSON (Client responsibility) | |
| * **Function Execution:** JSON -> Python Result -> LLM (Framework responsibility via `@use_function_invocation`) | |
| ### B. Reference Implementation | |
| ```python | |
| from agent_framework import BaseChatClient | |
| from agent_framework._tools import use_function_invocation | |
| from agent_framework.observability import use_observability | |
| from agent_framework._middleware import use_chat_middleware | |
| # 1. Apply decorators in this EXACT order | |
| @use_function_invocation | |
| @use_observability | |
| @use_chat_middleware | |
| class NewProviderChatClient(BaseChatClient): | |
| # 2. DO NOT set this unless you know what you are doing | |
| # __function_invoking_chat_client__ = True <-- DELETE THIS | |
| async def _inner_get_response(self, ...): | |
| # 3. Parse API response -> FunctionCallContent | |
| # 4. Return ChatResponse with contents=[FunctionCallContent(...)] | |
| pass | |
| async def _inner_get_streaming_response(self, ...): | |
| # 5. Yield FunctionCallContent when tool calls are detected | |
| pass | |
| ``` | |
| --- | |
| ## 5. Known Issues & Gotchas | |
| * **~~P1 Bug - Premature Marker Setting~~ (FIXED):** The `HuggingFaceChatClient` previously set `__function_invoking_chat_client__ = True` in the class body, which caused `@use_function_invocation` to skip wrapping. **Resolution:** Marker removed; decorator now sets it correctly. See `docs/bugs/P1_FREE_TIER_TOOL_EXECUTION_FAILURE.md`. | |
| * **HuggingFace Provider Routing:** Qwen2.5-7B-Instruct routes to Together.ai (not native HF). Tool call parsing may be inconsistent with complex multi-agent prompts. | |
| * **Model Hallucination:** If tool execution fails (due to incorrect wiring), models like Qwen2.5-7B will often **hallucinate** fake tool results as text. Always verify `AgentRunResponse` contains actual `FunctionResultContent`. | |
| --- | |
| ## 6. Verification Checklist | |
| When adding or modifying a ChatClient: | |
| - [ ] Decorators applied in correct order: `@use_function_invocation` β `@use_observability` β `@use_chat_middleware` | |
| - [ ] `__function_invoking_chat_client__` is NOT set in class body (unless implementing custom execution loop) | |
| - [ ] Verify `@use_function_invocation` decorator actually wraps methods (check `__wrapped__` attribute at runtime) | |
| - [ ] Tool calls parsed into `FunctionCallContent` objects | |
| - [ ] Streaming yields `FunctionCallContent` at end of stream | |
| - [ ] Run `make check` to verify all tests pass | |