# -*- coding: utf-8 -*- """AgentScope app factory.""" from typing import Type, TYPE_CHECKING, Any from ._lifespan import lifespan from .rag.blob_store import BlobStoreBase, LocalBlobStore from .rag.knowledge_base_manager import KnowledgeBaseManagerBase from .workspace_manager import WorkspaceManagerBase from ._router import ( agent_router, chat_router, credential_router, knowledge_base_router, model_router, tts_model_router, schedule_router, session_router, workspace_router, ) from ._types import AgentMiddlewareFactory, AgentToolFactory, SubAgentTemplate from .message_bus import MessageBus from .storage import StorageBase from ..agent import Agent from ..credential import CredentialFactory, CredentialBase from ..rag import ( ApproxTokenChunker, ChunkerBase, ParserBase, TextParser, ) from .._version import __version__ if TYPE_CHECKING: from fastapi import FastAPI from fastapi.middleware import Middleware as FastAPIMiddleware else: FastAPI = Any FastAPIMiddleware = Any def create_app( storage: StorageBase, message_bus: MessageBus, workspace_manager: WorkspaceManagerBase, knowledge_base_manager: KnowledgeBaseManagerBase | None = None, knowledge_parsers: list[ParserBase] | dict[str, ParserBase] | None = None, knowledge_chunker: ChunkerBase | None = None, blob_store: BlobStoreBase | None = None, enable_index_worker: bool = True, *, extra_credentials: list[Type[CredentialBase]] | None = None, extra_middlewares: list[FastAPIMiddleware] | None = None, extra_agent_middlewares: AgentMiddlewareFactory | None = None, extra_agent_tools: AgentToolFactory | None = None, custom_subagent_templates: list[SubAgentTemplate] | None = None, custom_agent_cls: Type[Agent] | None = None, title: str = "AgentScope", version: str = __version__, ) -> FastAPI: """Create and configure a FastAPI application. This is the primary entry point for embedding AgentScope into an existing service or running it standalone. All built-in routers are registered automatically; pass ``extra_middlewares`` to add your own. Usage — standalone:: app = create_app( storage=RedisStorage(), message_bus=RedisMessageBus(), workspace_manager=LocalWorkspaceManager(), ) uvicorn.run(app, host="0.0.0.0", port=8000) Usage — mount onto an existing app:: root = FastAPI() agentscope_app = create_app( storage=RedisStorage(), message_bus=RedisMessageBus(), workspace_manager=LocalWorkspaceManager(), ) root.mount("/agentscope", agentscope_app) Args: storage (`StorageBase`): The storage backend. Its lifecycle (``__aenter__`` / ``__aexit__``) is managed by the app lifespan. message_bus (`MessageBus`): The live message bus used for cross-session inbox delivery and idle-session triggers. Required — the bus is intentionally decoupled from ``storage`` so the persistence backend (e.g. SQL) can differ from the transport backend (Redis). Its lifecycle is also managed by the app lifespan. workspace_manager (`WorkspaceManagerBase`): The workspace manager. Required — every chat run and every ``/workspace`` endpoint depends on it. Its lifecycle ( ``__aenter__`` / ``__aexit__``) is managed by the app lifespan. Pass a :class:`~agentscope.app._manager. LocalWorkspaceManager` for local-directory workspaces. knowledge_base_manager (`KnowledgeBaseManagerBase | None`, \ optional): The knowledge base manager that owns knowledge base lifecycle and serves :class:`~agentscope.rag.KnowledgeBase` runtime handles to both HTTP service and agent code. The manager carries its own vector store instance — its ``__aenter__`` / ``__aexit__`` enter and release that vector store, so the caller does not pass the vector store separately. ``None`` disables knowledge base endpoints entirely. knowledge_parsers (`list[ParserBase] | dict[str, ParserBase] | \ None`, optional): Parsers registered for knowledge base document uploads. Pass a **list** to have the service route by each parser's ``supported_media_types`` (later entries override earlier ones for overlapping types, with a warning); pass a **dict** ``media_type → parser`` for explicit routing (one parser bound to multiple types, type aliases, ...). Defaults to ``[TextParser()]`` when ``knowledge_base_manager`` is set. knowledge_chunker (`ChunkerBase | None`, optional): The chunker shared across every knowledge base. Defaults to :class:`~agentscope.rag.ApproxTokenChunker()` when ``knowledge_base_manager`` is set. blob_store (`BlobStoreBase | None`, optional): Backend storing uploaded document bytes between the upload endpoint and the indexing worker. Required when ``knowledge_base_manager`` is set; defaults to :class:`~agentscope.app.rag.blob_store.LocalBlobStore` rooted at ``./blobs``. Its lifecycle (``__aenter__`` / ``__aexit__``) is managed by the app lifespan. enable_index_worker (`bool`, defaults to ``True``): When ``True`` (embedded deployment) the API process starts an :class:`~agentscope.app._service.IndexWorker` and an :class:`~agentscope.app._service.IndexSweeper` in its lifespan, and dispatches indexing tasks via an in-process queue. When ``False`` (dedicated deployment) the API process performs no indexing — a separate worker process is expected to consume tasks from the message bus. No effect when ``knowledge_base_manager`` is ``None``. extra_credentials (`list[Type[CredentialBase]] | None`, optional): Additional :class:`~agentscope.credential.CredentialBase` subclasses to register before the app starts. Equivalent to calling :func:`~agentscope.credential.CredentialFactory. register_credential` for each class. extra_middlewares (`list[Middleware] | None`, optional): Additional ASGI middlewares to add to the application. extra_agent_middlewares (`AgentMiddlewareFactory | None`, optional): An async factory ``(user_id, agent_id, session_id) -> awaitable of list[MiddlewareBase]`` that produces extra :class:`~agentscope.middleware.MiddlewareBase` instances to attach to the agent on each invocation. Called once per agent assembly (i.e. per chat turn / scheduled trigger), so it can return user/session-specific middleware (auth, audit logging, tenant isolation, etc.). The returned middlewares are appended to the framework-supplied ones (e.g. ``ToolOffloadMiddleware``). extra_agent_tools (`AgentToolFactory | None`, optional): An async factory ``(user_id, agent_id, session_id) -> awaitable of list[ToolBase]`` that produces extra :class:`~agentscope.tool.ToolBase` instances to register in the agent's toolkit on each invocation. Useful when tool availability depends on the caller (per-tenant integrations, user-specific credentials). The returned tools are added to the workspace-derived tools in the toolkit's ``"basic"`` group. custom_subagent_templates (`list[SubAgentTemplate] | None`, optional): Reusable blueprints for sub-agent creation within teams. Each template defines a sub-agent *type* (e.g. ``"researcher"``, ``"coder"``) with pre-configured system prompt, context config, ReAct config, permission context, and task context. When registered, the ``AgentCreate`` tool exposes a ``subagent_type`` parameter so the leader agent can route to the appropriate template. See :class:`~agentscope.app._types.SubAgentTemplate` for details. custom_agent_cls (`Type[Agent] | None`, optional): A custom :class:`~agentscope.agent.Agent` subclass to use when assembling agents. When ``None`` (default), the built-in :class:`~agentscope.agent.Agent` is used. title (`str`, defaults to ``"AgentScope"``): OpenAPI title shown in the docs UI. version (`str`, defaults to the package version): API version shown in the docs UI. Returns: `FastAPI`: A fully configured application ready to serve requests. """ from fastapi import FastAPI # Register any user-supplied credential types before the app starts for cls in extra_credentials or []: CredentialFactory.register_credential(cls) app = FastAPI(title=title, version=version, lifespan=lifespan) # Attach shared state that lifespan and dependencies read from app.state app.state.storage = storage app.state.message_bus = message_bus app.state.workspace_manager = workspace_manager app.state.knowledge_base_manager = knowledge_base_manager app.state.extra_agent_middlewares = extra_agent_middlewares app.state.extra_agent_tools = extra_agent_tools app.state.custom_agent_cls = custom_agent_cls # Parser / chunker / blob-store defaults only make sense when the # KB feature is actually enabled. When ``knowledge_base_manager`` is # ``None`` every KB endpoint is disabled, so leaving these as ``None`` # avoids unused imports being eagerly constructed at app startup. if knowledge_base_manager is not None: app.state.knowledge_parsers = ( knowledge_parsers if knowledge_parsers is not None else [TextParser()] ) app.state.knowledge_chunker = knowledge_chunker or ApproxTokenChunker() app.state.blob_store = ( blob_store if blob_store is not None else LocalBlobStore(root_dir="./blobs") ) else: app.state.knowledge_parsers = knowledge_parsers app.state.knowledge_chunker = knowledge_chunker app.state.blob_store = blob_store app.state.enable_index_worker = ( enable_index_worker and knowledge_base_manager is not None ) # Validate custom sub-agent templates for duplicate types and store in # app.state templates = custom_subagent_templates or [] seen_types: set[str] = set() duplicates: set[str] = set() for t in templates: if t.type in seen_types: duplicates.add(t.type) seen_types.add(t.type) if duplicates: raise ValueError( f"Duplicate sub_agent_template type(s): {duplicates}", ) app.state.custom_subagent_templates = {t.type: t for t in templates} # Built-in routers for router in ( agent_router, chat_router, credential_router, knowledge_base_router, schedule_router, session_router, workspace_router, model_router, tts_model_router, ): app.include_router(router) # Optional extra middlewares for middleware in extra_middlewares or []: app.add_middleware(middleware.cls, **middleware.kwargs) return app