When designing or extending APIs, treat existing “core” interface contracts as stable and enforce exact compatibility (method signature + parameter semantics + return shapes) across base classes and all implementations. If you need different inputs/behavior for a subset of modules, add an adapter/compatibility layer in the business layer instead of changing the core interface.

Practical rules:

Example: adapter-based compatibility (don’t modify core)

# Core interface (unchanged)
class ChatPromptTemplate:
    def generate_input_values(self, *, question: str, **kwargs):
        ...

# Business-layer adapter
class ChatDashboardPromptTemplate(ChatPromptTemplate):
    def generate_input_values(self, *, input: str, **kwargs):
        # map new variable name to old core contract
        return super().generate_input_values(question=input, **kwargs)

Example: contract-consistent retriever base

from typing import Any, Tuple

class GraphRetrieverBase:
    async def retrieve(self, input: Any) -> Tuple["Graph", Any]:
        raise NotImplementedError

# Implementation conforms to the base signature
class DocumentGraphRetriever(GraphRetrieverBase):
    async def retrieve(self, input):
        # input can be Union[Graph, List[str], List[List[float]]] in practice
        graph = ...
        extra = None
        return graph, extra

This prevents runtime failures like “takes N positional arguments but M were given” and avoids breaking unrelated modules that depend on the core contract.