This commit is contained in:
@@ -10,7 +10,7 @@ from typing import Literal
|
||||
import httpx
|
||||
from langchain_core.tools import tool
|
||||
from langchain_openai import ChatOpenAI
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from graph.agent import agent_manager
|
||||
|
||||
@@ -31,6 +31,15 @@ class ModelResult(BaseModel):
|
||||
requirement_response: str = Field(min_length=1, max_length=5000)
|
||||
experience_proposal: "ExperienceProposal | None" = None
|
||||
|
||||
@field_validator("requirement_response", mode="before")
|
||||
@classmethod
|
||||
def normalize_requirement_response(cls, value):
|
||||
if isinstance(value, dict):
|
||||
return ";".join(f"{key}:{item}" for key, item in value.items())
|
||||
if isinstance(value, list):
|
||||
return ";".join(str(item) for item in value)
|
||||
return value
|
||||
|
||||
|
||||
class ExperienceProposal(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
@@ -49,16 +58,21 @@ ModelResult.model_rebuild()
|
||||
def configuration():
|
||||
endpoint = os.getenv("XINGBANG_LOCAL_MODEL_URL", "")
|
||||
if not endpoint:
|
||||
return {"available": False, "reason": "未配置内网模型,语义核查未执行"}
|
||||
return {"available": False, "reason": "未配置受信模型,语义核查未执行"}
|
||||
url = urlparse(endpoint)
|
||||
allowed = url.hostname == "localhost"
|
||||
official_deepseek = (
|
||||
url.scheme == "https"
|
||||
and url.hostname == "api.deepseek.com"
|
||||
and url.port in (None, 443)
|
||||
)
|
||||
allowed = url.hostname == "localhost" or official_deepseek
|
||||
try:
|
||||
address = ipaddress.ip_address(url.hostname or "")
|
||||
allowed = address.is_loopback or any(address in ipaddress.ip_network(net) for net in ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16") if address.version == 4)
|
||||
except ValueError:
|
||||
pass
|
||||
if url.scheme not in ("http", "https") or not allowed or url.username or url.password or url.query or url.fragment:
|
||||
raise ValueError("模型须为已批准的内网地址,不允许凭据或参数嵌入 URL")
|
||||
raise ValueError("模型须为已批准的内网地址或 DeepSeek 官方 HTTPS 地址,不允许凭据或参数嵌入 URL")
|
||||
return {"available": True, "endpoint": endpoint, "name": os.getenv("XINGBANG_LOCAL_MODEL_NAME", "local-model")}
|
||||
|
||||
|
||||
@@ -69,7 +83,8 @@ PROMPT = """你是兴邦项目核查助手,必须通过受限工具读取本
|
||||
输出一个JSON对象,只含 findings 和 requirement_response。findings 每项字段为 code(稳定英文码)、title、result
|
||||
(pass/issue/unknown/not_applicable)、evidence_ids(真实证据ID数组)、fields(标准字段数组)、suggested_action。
|
||||
没有依据不得判定pass/issue。缺失资料标unknown,证据只能引用冻结快照。不得引用其他项目经验中的客户信息。
|
||||
requirement_response逐项说明是否完成用户要求,无法验证要明确说明。不要Markdown围栏。"""
|
||||
requirement_response必须是一个普通字符串,不得输出对象或数组;逐项说明是否完成用户要求,无法验证要明确说明。
|
||||
示例顶层结构:{"findings": [], "requirement_response": "已读取授权事实并完成核查。"}。不要Markdown围栏。"""
|
||||
|
||||
|
||||
def invoke(skill, snapshot, checks, rules, requirement, experiences, model_config, budget, cancelled=lambda: False, feedback=None):
|
||||
|
||||
@@ -602,10 +602,17 @@ class ProjectAssistantService(DocumentsMixin, ExperiencesMixin):
|
||||
if current["data_domain"] != "demo":
|
||||
raise ValueError("禁止重置正式项目")
|
||||
records = [dict(r) for r in db.execute("SELECT * FROM assistant_objects WHERE project_id=? OR (kind='project' AND id=?)", (item["id"], item["id"]))]
|
||||
put(db, "reset_archive", {"id": uid("reset"), "source_project_id": item["id"], "records": records, "actor": actor, "created_at": now()}, immutable=True)
|
||||
events = [dict(r) for r in db.execute("SELECT * FROM assistant_events WHERE project_id=? ORDER BY seq", (item["id"],))]
|
||||
time_events = [dict(r) for r in db.execute("SELECT * FROM business_time_events WHERE project_id=? ORDER BY started_at", (item["id"],))]
|
||||
put(db, "reset_archive", {"id": uid("reset"), "source_project_id": item["id"], "records": records,
|
||||
"events": events, "time_events": time_events, "actor": actor, "created_at": now()}, immutable=True)
|
||||
db.execute("DELETE FROM assistant_jobs WHERE project_id=?", (item["id"],))
|
||||
db.execute("DELETE FROM assistant_idempotency WHERE object_id IN (SELECT id FROM assistant_objects WHERE project_id=?)", (item["id"],))
|
||||
db.execute("DELETE FROM assistant_objects WHERE project_id=? OR (kind='project' AND id=?)", (item["id"], item["id"]))
|
||||
db.execute("DELETE FROM assistant_events WHERE project_id=?", (item["id"],))
|
||||
db.execute("DELETE FROM business_time_events WHERE project_id=?", (item["id"],))
|
||||
self._seed_project(db, item, "demo")
|
||||
self._seed_timeline(db, item)
|
||||
event(db, item["id"], "demo.reset", actor, {"recoverable": True})
|
||||
return {"status": "reset", "data_domain": "demo", "projects": len(fixtures["projects"]), "recoverable": True}
|
||||
return {"status": "reset", "data_domain": "demo", "projects": len(fixtures["projects"]),
|
||||
"recoverable": True, "history_reset": True}
|
||||
|
||||
Reference in New Issue
Block a user