Overview
FIM One is built around a set of thin abstract base classes — one per swappable component. Every component has a single responsibility and a minimal interface. You implement the abstract methods, wire the instance into the appropriate registry or injector, and the rest of the system uses your implementation automatically.| Extension point | Base class | File | Registration |
|---|---|---|---|
| LLM provider | BaseLLM | core/model/base.py | ModelRegistry.register() |
| Tool | BaseTool | core/tool/base.py | Drop a file in builtin/ |
| Memory | BaseMemory | core/memory/base.py | Constructor injection |
| Embedding | BaseEmbedding | core/embedding/base.py | Constructor injection |
| Image generation | BaseImageGen | core/image_gen/base.py | Constructor injection |
| Reranker | BaseReranker | core/reranker/base.py | Constructor injection |
| Web fetch backend | BaseWebFetch | core/web/fetch/base.py | Constructor injection |
| Web search backend | BaseWebSearch | core/web/search/base.py | Constructor injection |
| RAG retriever | BaseRetriever | rag/base.py | Constructor injection |
| Document loader | BaseLoader | rag/loaders/base.py | Loader registry / injection |
| Text chunker | BaseChunker | rag/chunking/base.py | Constructor injection |
Custom LLM provider
BaseLLM has two required methods — chat and stream_chat — plus an optional abilities property that tells the rest of the system what the model can do.
Registration via ModelRegistry
ModelRegistry maps names to BaseLLM instances and resolves by role. The system uses four built-in roles: general, fast, compact, and vision. You can add your own.
abilities dict is the contract between the LLM and the ReAct engine. When tool_call=True and the agent was created with use_native_tools=True, the engine will use native function calling. Otherwise it falls back to JSON mode automatically.
Custom tool
Tools are the most common extension.BaseTool has three required pieces: name, description, and run. Everything else has sensible defaults.
Auto-discovery
Drop your file insrc/fim_one/core/tool/builtin/. The discover_builtin_tools() scanner will find any concrete (non-abstract) BaseTool subclass automatically — no manual registration needed.
_SKIP_AUTO_DISCOVER. Use that set for tools that require external configuration (e.g. an API key) and need to be conditionally instantiated at startup.
Signalling unavailability
Overrideavailability() to surface a message in the tool catalog when a dependency is missing:
Rich results with artifacts
Return aToolResult instead of a plain str when your tool produces files:
Custom memory
BaseMemory is the persistence layer for conversation history. Three methods: add_message, get_messages, clear.
ReActAgent(llm=llm, memory=RedisMemory(conv_id, url)).
Custom embedding
BaseEmbedding provides two methods: embed_texts (batch) and embed_query (single), plus a dimension property.
embed_texts and embed_query exists because many embedding models (e.g. E5, BGE) use different prefixes for documents vs. queries to improve retrieval quality.
Custom image generation
BaseImageGen has a single method generate. It saves the image to output_dir and returns an ImageResult with the file path and a server-relative URL.
Custom reranker
BaseReranker takes a query and a list of document strings and returns them reordered with scores.
Custom web backends
Web fetch
BaseWebFetch fetches a URL and returns its content as Markdown or plain text.
Web search
BaseWebSearch returns a ranked list of SearchResult objects.
Custom RAG components
The RAG pipeline has three independently swappable stages: loading, chunking, and retrieval.Document loader
BaseLoader turns a file path into a list of LoadedDocument objects. PDF loaders typically return one document per page.
Text chunker
BaseChunker splits text into Chunk objects. MAX_CHUNK_SIZE = 6000 characters is the hard ceiling — chunk sizes above this can overflow the Jina Embeddings v3 token window.
Retriever
BaseRetriever queries any backend and returns ranked Document objects.
Design principles
A few patterns are consistent across all base classes that make custom implementations easier to write correctly: Async-first. Every method isasync def. Even if your implementation is synchronous, wrap it with asyncio.to_thread() rather than blocking the event loop.
String output from tools. BaseTool.run() returns str (or ToolResult). The LLM only ever sees text — tool implementations are responsible for serializing complex data into a readable format.
Minimal interfaces. Each base class defines the smallest contract needed. BaseMemory is three methods; BaseWebFetch is one. You are never required to implement functionality you don’t need.
Composition over inheritance. The base classes are interfaces, not frameworks. You inject your implementation at construction time; the runtime never monkey-patches or subclasses it further.