ReMe/reme4/components/service/base_service.py
jinliyl a4efc0f776
refactor(reme4): restructure steps packages (#258)
* fix(bm25_index): 修正BM25索引计算中的文档长度归一化问题

修复了在计算BM25相似度时对文档长度进行不正确归一化的bug,确保所有查询都能得到准确的相关性评分。

* up

* up

* up

* up

* up

* up

* up

* up

* up

* up

* up

* up

* up

* up

* up

* up

* up

* refactor(steps): Rename and adjust indexing step logic

- Rename `scan_changes.py` and `reindex.py` to `clear_and_scan.py`
- Update implementation details of `ScanChangesStep` and `ClearAndScanStep`
- Modify the scheduling mechanism in `WatchChangesStep`
- Adjust step registration and parameter configuration in config files
- Update related tests to align with the new interface changes

* up

* feat(daily): replace daily CRUD operations with slug provisioning approach

* refactor(tests): migrate CRUD step tests from HTTP server to direct LocalFileStore

* up

* up

* up

* up

---------

Co-authored-by: huangsen <huangsen.huang@alibaba-inc.com>
2026-05-28 14:30:30 +08:00

77 lines
2.7 KiB
Python

"""Base class for services that expose jobs over a network protocol."""
import json
import os
from abc import abstractmethod
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING
from ..base_component import BaseComponent
from ..job.base_job import BaseJob
from ...constants import REME_SERVICE_INFO
from ...enumeration import ComponentEnum
if TYPE_CHECKING:
from ...application import Application
class BaseService(BaseComponent):
"""Skeleton for services (HTTP, MCP, ...) that turn jobs into endpoints or tools."""
component_type = ComponentEnum.SERVICE
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Underlying framework instance (FastAPI, FastMCP, ...); populated by build_service().
self.service = None
# ----- Subclass contract ---------------------------------------------
@abstractmethod
def build_service(self, app: "Application") -> None:
"""Instantiate and configure the underlying server framework."""
@abstractmethod
def add_job(self, job: BaseJob) -> None:
"""Register a single job as a callable endpoint or tool."""
@abstractmethod
def start_service(self, app: "Application") -> None:
"""Block on serving requests until shutdown."""
# ----- Shared helpers ------------------------------------------------
def _lifespan(self, app: "Application", host: str, port: int):
"""Build an async-context lifespan that brackets the server with app start/close.
Publishes the bound address via the REME_SERVICE_INFO environment variable so
in-process clients can discover where this service is listening.
"""
@asynccontextmanager
async def lifespan(_):
await app.start()
service_info = json.dumps({"host": host, "port": port})
os.environ[REME_SERVICE_INFO] = service_info
self.logger.info(f"{self.name} started: {REME_SERVICE_INFO}={service_info}")
yield
await app.close()
return lifespan
def add_jobs(self, app: "Application") -> None:
"""Register every job whose enable_serve flag is True."""
for name, job in app.context.jobs.items():
if not job.enable_serve:
continue
try:
self.add_job(job)
self.logger.info(f"Added job: {name}")
except Exception as e:
self.logger.error(f"Failed to add job {name}: {e}")
def run_app(self, app: "Application") -> None:
"""Build the service, register jobs, then start serving (blocking)."""
self.build_service(app)
self.add_jobs(app)
self.start_service(app)