feat(services): assignment 6 full - form/business key mapping and stream write-back
- fields_registry: FORM_TO_BUSINESS_KEY bidirectional mapping between the graph's six form keys (has_injury...) and the acd business keys (ywrysw...); bjrjs/ywmtc have no form source (set_info only); business_patch_from_form maps graph diffs to whitelisted business keys and filters internal/unknown keys - app_service: chat injection now maps repository business keys back to form keys; both complete() and stream() write back field diffs (FormUpdate events captured in stream, closing the skeleton gap); form keys never leak into the business repository - docs/assignment6-compat.md: mapping table documented, stream write-back backlog item resolved - tests: mapping coverage, write-back mapping end-to-end, reverse mapping injection (bjrjs/ywmtc excluded), stream FormUpdate write-back Verified: 205 unit tests
This commit is contained in:
+24
-11
@@ -13,11 +13,13 @@
|
||||
endpoint 只负责校验、协议(SSE / 响应模型)与依赖注入。
|
||||
"""
|
||||
|
||||
from ..backends.chat import ChatBackend, ChatInput, ChatResult
|
||||
from ..backends.chat import ChatBackend, ChatInput, ChatResult, FormUpdate
|
||||
from .fields_registry import (
|
||||
FIELD_REGISTRY,
|
||||
GROUP_KEYS,
|
||||
BUSINESS_TO_FORM_KEY,
|
||||
FieldGroup,
|
||||
business_patch_from_form,
|
||||
parse_field_value,
|
||||
to_external_string,
|
||||
)
|
||||
@@ -37,8 +39,12 @@ class AccidentAppService:
|
||||
# ---- chat:图每轮读取最新业务状态,产出差量写回 ----
|
||||
|
||||
def _injected_fields(self, session_id: str) -> dict:
|
||||
"""repository 业务字段 -> 图输入的分组注入(acd 进 accident_fields,
|
||||
两组人员进 person_fields,与 AgentState 的键结构对齐)。"""
|
||||
"""repository 业务字段 -> 图输入的分组注入。
|
||||
|
||||
acd 业务键经 BUSINESS_TO_FORM_KEY 映射回图表单键
|
||||
(无表单来源的 bjrjs/ywmtc 不注入);人员业务键与图的
|
||||
person_fields 同构,原样注入。
|
||||
"""
|
||||
business = self._repository.load(session_id)
|
||||
if not business:
|
||||
return {}
|
||||
@@ -48,11 +54,19 @@ class AccidentAppService:
|
||||
if spec is None:
|
||||
continue
|
||||
if spec.group is FieldGroup.ACD:
|
||||
injected["accident_fields"][key] = value
|
||||
form_key = BUSINESS_TO_FORM_KEY.get(key)
|
||||
if form_key is not None:
|
||||
injected["accident_fields"][form_key] = value
|
||||
else:
|
||||
injected["person_fields"][key] = value
|
||||
return injected
|
||||
|
||||
def _writeback_patch(self, session_id: str, patch: dict) -> None:
|
||||
"""图产出的差量(表单键/人员键)经映射落 repository 业务键。"""
|
||||
business_patch = business_patch_from_form(patch)
|
||||
if business_patch:
|
||||
self._repository.apply_update(session_id, business_patch)
|
||||
|
||||
async def chat(self, chat_input: ChatInput) -> ChatResult:
|
||||
"""非流式一轮对话:注入最新业务字段 -> 执行图 -> 写回字段差量。"""
|
||||
injected = self._injected_fields(chat_input.session_id)
|
||||
@@ -65,15 +79,14 @@ class AccidentAppService:
|
||||
injected_fields=injected or None,
|
||||
)
|
||||
)
|
||||
# 图产出的字段差量(turn_patch / 完整字段)写回 repository,
|
||||
# 图产出的字段差量写回 repository,
|
||||
# 使 set/get_info 与后续轮次看到同一事实来源
|
||||
patch = result.form_update if isinstance(result.form_update, dict) else {}
|
||||
if patch:
|
||||
self._repository.apply_update(chat_input.session_id, patch)
|
||||
if isinstance(result.form_update, dict):
|
||||
self._writeback_patch(chat_input.session_id, result.form_update)
|
||||
return result
|
||||
|
||||
async def stream(self, chat_input: ChatInput):
|
||||
"""流式代理:字段注入与写回语义同 chat(),事件流原样透传。"""
|
||||
"""流式代理:字段注入同 chat();FormUpdate 事件捕获写回后透传。"""
|
||||
injected = self._injected_fields(chat_input.session_id)
|
||||
result_input = ChatInput(
|
||||
session_id=chat_input.session_id,
|
||||
@@ -82,9 +95,9 @@ class AccidentAppService:
|
||||
client_mode=chat_input.client_mode,
|
||||
injected_fields=injected or None,
|
||||
)
|
||||
# 骨架阶段原样透传事件流;字段差量写回在作业 6 完整版随
|
||||
# FormUpdate 事件捕获补齐(对照计划作业 8 的真实增量)。
|
||||
async for event in self._backend.stream(result_input):
|
||||
if isinstance(event, FormUpdate) and isinstance(event.data, dict):
|
||||
self._writeback_patch(chat_input.session_id, event.data)
|
||||
yield event
|
||||
|
||||
# ---- set_info / get_info:零 LLM、零辅助对话 ----
|
||||
|
||||
@@ -93,6 +93,39 @@ INTERNAL_FIELD_KEYS = frozenset(
|
||||
}
|
||||
)
|
||||
|
||||
# 图表单键(accident_fields,作业 4 起使用)与 get_info 业务键的映射
|
||||
# (作业 6 建立;对照工作流「更新表单」白名单与 get_info 分组):
|
||||
# 表单六键覆盖 acd 分组中的六个对应项;bjrjs(报警人是否驾驶员)与
|
||||
# ywmtc(是否撞摩托车)无表单来源,只能由 set_info 直接写入。
|
||||
FORM_TO_BUSINESS_KEY: dict[str, str] = {
|
||||
"has_injury": "ywrysw",
|
||||
"has_non_motor_vehicle_hit": "ywfjdc",
|
||||
"is_on_scene": "sfsgxc",
|
||||
"vehicle_count": "jdcsl",
|
||||
"accident_time": "sgfssj",
|
||||
"accident_cause": "sgyy",
|
||||
}
|
||||
BUSINESS_TO_FORM_KEY: dict[str, str] = {
|
||||
business: form for form, business in FORM_TO_BUSINESS_KEY.items()
|
||||
}
|
||||
|
||||
|
||||
def business_patch_from_form(form_patch: dict) -> dict:
|
||||
"""图产出的表单差量 -> repository 业务键差量(白名单内)。
|
||||
|
||||
只收录能落到注册表的键:表单六键经映射转换,其余键若本身
|
||||
是注册表业务键(如 chat 核实产出的人员键)原样收录。
|
||||
"""
|
||||
patch: dict = {}
|
||||
for key, value in (form_patch or {}).items():
|
||||
business_key = FORM_TO_BUSINESS_KEY.get(key, key)
|
||||
spec = FIELD_REGISTRY.get(business_key)
|
||||
if spec is None or not spec.writable:
|
||||
continue
|
||||
# 类型以注册表为准:图表单值已是内部类型,直接采用
|
||||
patch[business_key] = value
|
||||
return patch
|
||||
|
||||
|
||||
def parse_field_value(spec: FieldSpec, raw: str) -> Any:
|
||||
"""外部字符串 -> 内部强类型值;无法解析时抛 ValueError。
|
||||
@@ -136,6 +169,9 @@ __all__ = [
|
||||
"FIELD_REGISTRY",
|
||||
"GROUP_KEYS",
|
||||
"INTERNAL_FIELD_KEYS",
|
||||
"FORM_TO_BUSINESS_KEY",
|
||||
"BUSINESS_TO_FORM_KEY",
|
||||
"business_patch_from_form",
|
||||
"FieldGroup",
|
||||
"FieldSpec",
|
||||
"SensitiveLevel",
|
||||
|
||||
Reference in New Issue
Block a user