mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-06 12:59:09 +00:00
- command-ify the write use cases (RateRun / RetractRunRating; queries keep plain parameters, commands stay dumb data) - split the domain errors into exceptions.py, a peer of model.py (PEP 8 Error suffixes, AWS-style module name) - unify the aggregate->row mapping as _apply(row, feedback) so one explicit field list serves both the insert and the update path - drop the unused feedback.message_id column (migration 0011): feedback is bound to a run, nothing ever wrote or read the field - pin remove_for_run's equality semantics for user_id=None in the contract suite and fix the port docstring that contradicted both implementations
74 lines
2.9 KiB
Python
74 lines
2.9 KiB
Python
from typing import Protocol, runtime_checkable
|
|
|
|
from deerflow.domain.feedback.model import Feedback
|
|
|
|
|
|
@runtime_checkable
|
|
class FeedbackRepository(Protocol):
|
|
"""Output port for feedback persistence.
|
|
|
|
Implementations exchange domain objects only and translate storage
|
|
failures into domain errors -- no storage vocabulary (SQL, tables,
|
|
files) may leak through this contract.
|
|
"""
|
|
|
|
async def save(self, feedback: Feedback) -> Feedback:
|
|
"""Store the user's current rating for a run (idempotent upsert).
|
|
|
|
Creates the entry if absent; otherwise replaces rating/comment and
|
|
refreshes created_at while keeping the aggregate identity
|
|
(thread_id, run_id, user_id). Returns the stored state.
|
|
"""
|
|
...
|
|
|
|
async def latest_per_run_in_thread(self, thread_id: str, *, user_id: str | None) -> dict[str, Feedback]:
|
|
"""Return each run's current feedback across a whole thread, keyed by run_id.
|
|
|
|
Single bulk read used to badge the message list -- avoids one
|
|
query per run. Ownership filter: a non-None user_id restricts results to the
|
|
user's entries; None means no filtering (no-auth mode).
|
|
"""
|
|
...
|
|
|
|
async def latest_for_runs(self, thread_id: str, run_ids: set[str], *, user_id: str | None) -> dict[str, Feedback]:
|
|
"""Return current feedback for only the selected runs of a thread.
|
|
|
|
Paged variant of latest_per_run_in_thread: the message-list page
|
|
endpoint only needs badges for the runs on the current page.
|
|
Returns an empty mapping when run_ids is empty. Same ownership
|
|
filter as find_for_run.
|
|
"""
|
|
...
|
|
|
|
async def remove_for_run(self, thread_id: str, run_id: str, *, user_id: str | None) -> bool:
|
|
"""Retract the user's feedback for a run.
|
|
|
|
Returns True if an entry was removed, False if none existed.
|
|
Unlike the read methods, ownership here is an EQUALITY match, not a
|
|
filter: a non-None user_id removes only that user's entry, and None
|
|
matches only entries stored with a NULL owner (what no-auth mode
|
|
writes) -- it does NOT mean "remove regardless of owner". A delete
|
|
must never reach across owners, so the asymmetry with the reads'
|
|
"None = no filtering" is deliberate.
|
|
"""
|
|
...
|
|
|
|
|
|
class RunLookup(Protocol):
|
|
"""Narrow output port: the only question the feedback context asks
|
|
about runs -- which thread does a run belong to.
|
|
|
|
Deliberately not the full run store: depending on this one-method
|
|
contract keeps the feedback context decoupled from the execution
|
|
context's wide repository interface (interface segregation).
|
|
"""
|
|
|
|
async def thread_of(self, run_id: str) -> str | None:
|
|
"""Return the thread_id owning the given run, or None if the run
|
|
does not exist.
|
|
|
|
Used by the service to verify run ownership before writing
|
|
feedback (rejects cross-thread run ids as RunNotFoundError).
|
|
"""
|
|
...
|