feat(services): assignment 6 skeleton - app service, field registry, in-memory repository
- fields_registry: 24 business keys in acd/human1/human2 groups with
value_type, writable and SensitiveLevel metadata; strict bool/int
parsing (no bool('0') trap); INTERNAL_FIELD_KEYS protection list
- repository: SessionRepository protocol + InMemorySessionRepository
(idempotent delete; PostgreSQL swap-in is assignment 7)
- AccidentAppService: chat/set_info/get_info/delete_session share one
business state - chat injects latest repository fields into the graph
and writes back validated field diffs; set/get/delete make zero LLM
calls and zero helper conversations (includeInputInfo accepted but
unused); unknown get keys keep the '' JSON-empty-string contract,
unknown set keys are rejected by whitelist
- ChatInput.injected_fields neutral contract extension; LangGraphBackend
merges injected fields into graph input (set_info visible next chat)
- endpoints: thin layer - langgraph mode routes business state through
the app service (fastgpt mode keeps the legacy path); delete cleans
repository + graph checkpoint, idempotent
- tests: registry completeness (sfzmwh/sjwh/csbw included), app service
combos with LLM-call spy, HTTP acceptance flow (set -> get -> chat ->
groups -> delete -> no leak, idempotent re-delete)
- docs/assignment6-compat.md: behavior diff list vs FastGPT status quo
and the audit backlog (arbitrary set_info key usage, stream formUpdate
write-back, sensitive-field display boundary)
Verified: 200 unit tests (172 existing + 28 new)
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
"""会话业务状态 repository(作业 6-1/6-2)。
|
||||
|
||||
抽象出 repository 接口,使应用服务不依赖具体存储;
|
||||
作业 6 用内存实现跑通四接口,作业 7 再替换为 PostgreSQL 实现
|
||||
(对话 checkpoint 已由 LangGraph checkpointer 承担,本层只管
|
||||
业务字段——单一事实来源,图每轮读取最新值)。
|
||||
|
||||
语义约定:
|
||||
- 业务状态按外部业务键(hphm1、ywrysw...)扁平存储,值为内部强类型;
|
||||
- 不存在的会话视为空状态(读取返回 {}),与首轮 checkpoint 无字段一致;
|
||||
- delete 幂等:重复删除固定成功,不抛异常(行为进测试)。
|
||||
"""
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class SessionRepository(Protocol):
|
||||
"""业务字段存储接口(作业 7 由 PostgreSQL 实现替换)。"""
|
||||
|
||||
def load(self, session_id: str) -> dict:
|
||||
"""读取会话业务字段;无记录时返回空 dict。"""
|
||||
...
|
||||
|
||||
def apply_update(self, session_id: str, patch: dict) -> dict:
|
||||
"""合并差量并返回合并后的完整业务字段。"""
|
||||
...
|
||||
|
||||
def delete(self, session_id: str) -> None:
|
||||
"""删除会话业务状态;幂等,重复删除不抛异常。"""
|
||||
...
|
||||
|
||||
|
||||
class InMemorySessionRepository:
|
||||
"""内存实现:字典存储,仅用于单进程开发与测试。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._store: dict[str, dict] = {}
|
||||
|
||||
def load(self, session_id: str) -> dict:
|
||||
return dict(self._store.get(session_id) or {})
|
||||
|
||||
def apply_update(self, session_id: str, patch: dict) -> dict:
|
||||
state = self._store.setdefault(session_id, {})
|
||||
state.update(patch)
|
||||
return dict(state)
|
||||
|
||||
def delete(self, session_id: str) -> None:
|
||||
# 幂等:pop 的缺省值吞掉不存在的情况,重复删除行为固定为成功
|
||||
self._store.pop(session_id, None)
|
||||
|
||||
|
||||
__all__ = ["SessionRepository", "InMemorySessionRepository"]
|
||||
Reference in New Issue
Block a user