AI重构
This commit is contained in:
129
backend_service/src/prompting/composer.py
Normal file
129
backend_service/src/prompting/composer.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from typing import Dict, List, Sequence, Set
|
||||
|
||||
from .manifest_loader import ManifestPromptLoader
|
||||
|
||||
|
||||
@dataclass
|
||||
class PromptPackage:
|
||||
system_prompt: str
|
||||
user_augmentation: str
|
||||
allowed_nodes: Dict[str, List[str]]
|
||||
|
||||
|
||||
class PromptComposer:
|
||||
def __init__(self, prompts_dir: str):
|
||||
self.loader = ManifestPromptLoader(prompts_dir)
|
||||
self.prompts_dir = prompts_dir
|
||||
self._simple_prompt = self.loader.load_text_file("simple_mode_prompt.txt")
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def _cached_scene_parts(self, scene_mode: str) -> tuple[str, str, str, str]:
|
||||
# 固定骨架
|
||||
header = self._load_partial("header.txt")
|
||||
required_fields = self._load_partial("required_fields.txt")
|
||||
standard_template = self._load_partial("standard_template.txt")
|
||||
examples = self._load_partial("scene1_examples.txt" if scene_mode == "scene1" else "scene4_examples.txt")
|
||||
return header, required_fields, standard_template, examples
|
||||
|
||||
def _load_partial(self, file_name: str) -> str:
|
||||
path = os.path.join(self.prompts_dir, "partials", file_name)
|
||||
if not os.path.exists(path):
|
||||
return ""
|
||||
return self.loader.load_fragment_text(path).strip("\n")
|
||||
|
||||
def _load_nodes_payload(self) -> Dict[str, List[Dict[str, str]]]:
|
||||
core_path = os.path.join(self.prompts_dir, "partials", "core_nodes.json")
|
||||
if not os.path.exists(core_path):
|
||||
return {"actions": [], "conditions": [], "control_flow": [], "decorators": []}
|
||||
with open(core_path, "r", encoding="utf-8") as f:
|
||||
payload = json.load(f)
|
||||
return payload
|
||||
|
||||
def _slice_nodes(self, required_actions: Sequence[str], risk_flags: Sequence[str], scene_mode: str) -> Dict[str, List[str]]:
|
||||
payload = self._load_nodes_payload()
|
||||
base_actions: Set[str] = {"takeoff", "land", "fly_to_waypoint", "move_direction"}
|
||||
base_conditions: Set[str] = {"object_detected"}
|
||||
if scene_mode != "simple":
|
||||
base_actions.update({"rotate_search", "take_photos"})
|
||||
action_names = {a.get("name") for a in payload.get("actions", []) if isinstance(a, dict)}
|
||||
condition_names = {c.get("name") for c in payload.get("conditions", []) if isinstance(c, dict)}
|
||||
|
||||
selected_actions = set(required_actions) | (base_actions & action_names)
|
||||
selected_conditions = base_conditions & condition_names
|
||||
if "needs_manual_confirmation" in risk_flags and "manual_confirmation" in action_names:
|
||||
selected_actions.add("manual_confirmation")
|
||||
if "needs_emergency_return_rule" in risk_flags and "return_emergency" in action_names:
|
||||
selected_actions.add("return_emergency")
|
||||
if "needs_approach_target" in risk_flags and "approach_target" in action_names:
|
||||
selected_actions.add("approach_target")
|
||||
|
||||
ordered_actions = sorted(selected_actions)[:30]
|
||||
ordered_conditions = sorted(selected_conditions)[:30]
|
||||
return {"actions": ordered_actions, "conditions": ordered_conditions}
|
||||
|
||||
def _build_nodes_snippet(self, selected: Dict[str, List[str]]) -> str:
|
||||
payload = self._load_nodes_payload()
|
||||
selected_action_set = set(selected.get("actions", []))
|
||||
selected_condition_set = set(selected.get("conditions", []))
|
||||
slim_payload = {
|
||||
"actions": [a for a in payload.get("actions", []) if a.get("name") in selected_action_set],
|
||||
"conditions": [c for c in payload.get("conditions", []) if c.get("name") in selected_condition_set],
|
||||
"control_flow": payload.get("control_flow", []),
|
||||
"decorators": payload.get("decorators", []),
|
||||
}
|
||||
return "\n".join(
|
||||
[
|
||||
"## 一、核心节点定义(裁剪后)",
|
||||
"#### 1. 可用节点定义 (必须遵守)",
|
||||
"```json",
|
||||
json.dumps(slim_payload, ensure_ascii=False, indent=2),
|
||||
"```",
|
||||
]
|
||||
)
|
||||
|
||||
def compose(
|
||||
self,
|
||||
scene_mode: str,
|
||||
intent_type: str,
|
||||
required_actions: Sequence[str],
|
||||
risk_flags: Sequence[str],
|
||||
context_blocks: Dict[str, str],
|
||||
include_extra_examples: bool,
|
||||
) -> PromptPackage:
|
||||
if scene_mode == "simple":
|
||||
user_aug = self._build_user_augmentation(context_blocks)
|
||||
return PromptPackage(system_prompt=self._simple_prompt, user_augmentation=user_aug, allowed_nodes={})
|
||||
|
||||
header, required_fields, standard_template, examples = self._cached_scene_parts(scene_mode)
|
||||
selected_nodes = self._slice_nodes(required_actions, risk_flags, scene_mode)
|
||||
node_snippet = self._build_nodes_snippet(selected_nodes)
|
||||
|
||||
parts: List[str] = [header, node_snippet, required_fields, standard_template, examples]
|
||||
common_rules = self._load_partial("common_rules.txt")
|
||||
if common_rules:
|
||||
parts.append(common_rules)
|
||||
if include_extra_examples:
|
||||
extra = self._load_partial("system_extra_examples.txt")
|
||||
if extra:
|
||||
parts.append(extra)
|
||||
parts.append(f"## 任务意图标签\n- intent_type: `{intent_type}`")
|
||||
system_prompt = "\n\n".join(p for p in parts if p).strip()
|
||||
return PromptPackage(system_prompt=system_prompt, user_augmentation=self._build_user_augmentation(context_blocks), allowed_nodes=selected_nodes)
|
||||
|
||||
def _build_user_augmentation(self, context_blocks: Dict[str, str]) -> str:
|
||||
ordered = [("地点知识", "location"), ("任务模式", "pattern"), ("规则知识", "rules")]
|
||||
chunks: List[str] = []
|
||||
for label, key in ordered:
|
||||
value = (context_blocks.get(key) or "").strip()
|
||||
if value:
|
||||
chunks.append(f"【{label}】\n{value}")
|
||||
if not chunks:
|
||||
return ""
|
||||
return "\n\n---\n参考知识:\n" + "\n\n".join(chunks) + "\n---"
|
||||
|
||||
Reference in New Issue
Block a user