Files

133 lines
6.5 KiB
Python

"""公共 FastAPI 应用装配边界。
该模块位于 HTTP 路由模板和运行时生命周期之间,只负责创建应用实例、复制
不可变路由元数据、安装异常/middleware 以及绑定宿主提供的 resolver 和扩展。
数据库、Worker、OpenCode 等有副作用的资源只能在 lifespan 中装配。
"""
from __future__ import annotations
from collections.abc import Callable, Sequence
from contextlib import asynccontextmanager
from typing import Any
from fastapi import FastAPI
from backend.app_extensions import ApplicationExtension
from backend.database import ScopeContext
from backend.errors import ErrorBody
from backend.operation_policy import AllowAllOperationPolicy, OperationPolicyError
from backend.reverse_image import ReverseImageProviderBinding
from backend.scope import LocalScopeResolver, ScopeResolutionError, ScopeServiceFactory
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from runtime_telemetry import TelemetryConfig, start_telemetry
def _validate_resolver(scope_resolver: object) -> None:
"""校验应用装配所需的可信 resolver,不在缺失时安装 local fallback。"""
if scope_resolver is None:
raise ScopeResolutionError("应用必须显式配置 scope resolver")
if not any(callable(getattr(scope_resolver, name, None)) for name in ("resolve", "resolve_scope")) and not callable(scope_resolver):
raise ScopeResolutionError("scope resolver 不可调用")
if isinstance(scope_resolver, LocalScopeResolver) and scope_resolver.scope.scope_id != "local":
raise ScopeResolutionError("local resolver 只能绑定 local scope")
def _validate_operation_policy(operation_policy: object) -> None:
"""校验宿主 policy 的四阶段协议,避免启动后才暴露策略旁路。"""
if operation_policy is not None and not all(callable(getattr(operation_policy, name, None)) for name in ("probe", "acquire", "commit", "release")):
raise OperationPolicyError("operation_policy_unavailable")
def create_application(
*,
route_template: FastAPI,
lifespan: Callable[[FastAPI], Any],
scope_resolver: object,
service_factory: ScopeServiceFactory | None = None,
operation_policy: object | None = None,
callback_issuer: object | None = None,
callback_verifier: object | None = None,
agent_input_provider: Callable[[ScopeContext, Any], str | Any] | None = None,
workspace_provider: object | None = None,
model_capability_provider: object | None = None,
reverse_image_provider_binding: ReverseImageProviderBinding | None = None,
extensions: Sequence[ApplicationExtension] | None = None,
) -> FastAPI:
"""创建一个绑定可信依赖的公共应用。
参数输入是宿主装配时的不可变依赖;返回值包含与路由模板同顺序的路由和
middleware,但不会在 import/factory 阶段创建数据库、文件、Worker 或外部
客户端。真正资源初始化由传入的 lifespan 负责。
"""
_validate_resolver(scope_resolver)
_validate_operation_policy(operation_policy)
if reverse_image_provider_binding is not None and not isinstance(reverse_image_provider_binding, ReverseImageProviderBinding):
raise ValueError("reverse_image_provider_binding_invalid")
if service_factory is not None and reverse_image_provider_binding is not None:
# 自定义 factory 完全拥有 scope 服务装配,不能同时声明一个不会被它消费的绑定。
raise ValueError("reverse_image_provider_binding_requires_managed_factory")
configured_extensions = tuple(extensions or ())
telemetry_config = TelemetryConfig.from_env()
@asynccontextmanager
async def observed_lifespan(application: FastAPI):
"""把 SDK 生命周期与应用资源生命周期绑定,启动时报告配置状态。"""
runtime = start_telemetry()
application.state.telemetry_enabled = runtime is not None
try:
async with lifespan(application):
yield
finally:
if runtime is not None:
runtime.close()
created = FastAPI(
title=route_template.title,
version=route_template.version,
description=route_template.description,
lifespan=observed_lifespan,
responses={
400: {"model": ErrorBody},
403: {"model": ErrorBody},
404: {"model": ErrorBody},
409: {"model": ErrorBody},
422: {"model": ErrorBody},
503: {"model": ErrorBody},
},
)
# APIRoute/StaticFiles 只保存不可变路由元数据;复制引用不会共享请求 scope。
created.router.routes.extend(route_template.router.routes)
created.exception_handlers.update(route_template.exception_handlers)
for middleware in reversed(route_template.user_middleware):
created.add_middleware(middleware.cls, *middleware.args, **middleware.kwargs)
created.state.extensions = configured_extensions
created.state.expose_scope = True
for extension in configured_extensions:
register_routes = getattr(extension, "register_routes", None)
if callable(register_routes):
register_routes(created)
created.state.scope_resolver = scope_resolver
# policy、callback、workspace 和 resolver 属于不同信任边界,均只从同一个
# keyword-only 工厂进入应用,不使用模块级可变依赖。
created.state.operation_policy = operation_policy if operation_policy is not None else (AllowAllOperationPolicy() if isinstance(scope_resolver, LocalScopeResolver) else None)
created.state.callback_issuer = callback_issuer
created.state.callback_verifier = callback_verifier
if service_factory is not None:
created.state.service_factory = service_factory
created.state._scope_factory_managed = False
if agent_input_provider is not None:
created.state.agent_input_provider = agent_input_provider
if workspace_provider is not None:
created.state.workspace_provider = workspace_provider
if model_capability_provider is not None:
created.state.model_capability_provider = model_capability_provider
if reverse_image_provider_binding is not None:
created.state.reverse_image_provider_binding = reverse_image_provider_binding
if telemetry_config.enabled:
FastAPIInstrumentor.instrument_app(created, excluded_urls="/health", http_capture_headers_server_request=[], http_capture_headers_server_response=[])
return created
__all__ = ["create_application"]