"""判断需求涉及的范围、规划和资源需求""" import json from langchain_core.messages import HumanMessage, SystemMessage from app.clients.llm import get_llm from app.processor.base import BaseNode from app.processor.state import QueryState from app.prompt.scope import SCOPE_SYSTEM_PROMPT from app.schemas.change_scope import ChangeScope class ScopeNode(BaseNode): name = "scope_node" async def process(self, state: QueryState) -> QueryState: requirement = state["requirement"] if requirement.needs_clarification or requirement.intent not in ( "create_screen", "modify_screen", ): raise ValueError("ScopeNode 只处理信息完整的新建或修改需求") context = state["input_context"] llm_client = get_llm(ChangeScope) scope = await llm_client.ainvoke( [ SystemMessage(content=SCOPE_SYSTEM_PROMPT), HumanMessage( content=json.dumps( { "requirement": requirement.model_dump(mode="json"), "input_context": context.model_dump( mode="json", exclude_unset=True ), }, ensure_ascii=False, ) ), ] ) existing_ids = {node.id for node in context.screen.nodes} if unknown_ids := set(scope.target_ids) - existing_ids: raise ValueError(f"范围引用了不存在的节点: {sorted(unknown_ids)}") required_ids = { node_id for change in requirement.changes for node_id in change.target_ids } if missing_ids := required_ids - set(scope.target_ids): raise ValueError(f"范围遗漏了需求中的节点: {sorted(missing_ids)}") return {**state, "change_scope": scope} if __name__ == "__main__": import asyncio from pydantic import BaseModel from app.processor.nodes.input_node import InputNode from app.schemas.requirement import Requirement async def main(): # 直接提供已理解的需求,单独测试范围判断,仅需 LLM 配置 state: QueryState = { "request": { "prompt": "把销售额统计向右移动 50px,其他不变", "screen": { "canvas": {"width": 1920, "height": 1080}, "nodes": [ { "id": "sales-chart", "type": "bar-chart", "name": "销售额统计", "layout": {"x": 26, "y": 34.8, "width": 400, "height": 260}, } ], }, } } state = InputNode().process(state) state["requirement"] = Requirement( intent="modify_screen", goal="销售额统计向右移动 50px", changes=[ { "target_scope": "nodes", "target_ids": ["sales-chart"], "operation": "move", "parameters": {"delta_x": 50}, } ], needs_clarification=False, constraints=["其他不变"], ) node = ScopeNode() final_state = await node.process(state) print( json.dumps( { key: value.model_dump(mode="json") if isinstance(value, BaseModel) else value for key, value in final_state.items() }, ensure_ascii=False, indent=2, ) ) asyncio.run(main())