feat(research): streamline template details and enable bot versioning
Deploy production / deploy (push) Successful in 1m35s
Deploy production / deploy (push) Successful in 1m35s
This commit is contained in:
@@ -526,3 +526,62 @@ async def test_model_cannot_append_preparation_references_to_fixed_inputs(app, l
|
||||
assert response.status_code == 422 and "不能改变" in response.text
|
||||
async with app.state.sessions() as db:
|
||||
assert not await db.scalar(select(ResearchAsset).where(ResearchAsset.name == "untrusted"))
|
||||
|
||||
|
||||
async def test_template_definitions_bind_selected_fields_without_mutating_asset(logged_in, research_input):
|
||||
content = template()
|
||||
content["expression"] = "rank({field}) + rank({field})"
|
||||
content["variables"]["field"] = {"kind": "field", "field_type": "MATRIX", "description": "横截面字段"}
|
||||
saved = await logged_in.post("/api/v1/research/assets", json={"kind": "template", "content": content})
|
||||
assert saved.status_code == 201, saved.text
|
||||
asset = saved.json()
|
||||
assert asset["content"]["variables"]["field"] == {
|
||||
"kind": "field", "field_type": "MATRIX", "description": "横截面字段", "values": [],
|
||||
}
|
||||
body = expansion(research_input["id"], asset_id=asset["id"], version=1, mode="random", limit=2, seed=19)
|
||||
body.pop("template")
|
||||
first = await logged_in.post("/api/v1/research/experiments", json=body)
|
||||
second = await logged_in.post("/api/v1/research/experiments", json=body)
|
||||
assert first.status_code == second.status_code == 201, first.text
|
||||
assert first.json()["candidates"] == second.json()["candidates"]
|
||||
assert len(first.json()["candidates"]) == 2
|
||||
for candidate in first.json()["candidates"]:
|
||||
field = candidate["bindings"]["field"]
|
||||
assert research_input["field_types"][field] == "MATRIX"
|
||||
assert candidate["expression"] == f"rank({field}) + rank({field})"
|
||||
stored = (await logged_in.get(f"/api/v1/research/assets/{asset['id']}")).json()
|
||||
assert stored["content"] == asset["content"]
|
||||
|
||||
|
||||
async def test_empty_template_domains_fail_expansion_with_actionable_errors(logged_in, research_input):
|
||||
body = expansion(research_input["id"])
|
||||
body["template"]["variables"]["field"] = {"kind": "field", "field_type": "GROUP"}
|
||||
response = await logged_in.post("/api/v1/research/experiments", json=body)
|
||||
assert response.status_code == 422 and "没有匹配的 GROUP 字段" in response.text
|
||||
body["template"]["expression"] = "ts_mean(TEST_FIN_001, {window})"
|
||||
body["template"]["variables"] = {"window": {"kind": "integer", "description": "时间窗口"}}
|
||||
response = await logged_in.post("/api/v1/research/experiments", json=body)
|
||||
assert response.status_code == 422 and "变量 window 缺少候选取值" in response.text
|
||||
|
||||
|
||||
async def test_native_bot_creates_template_and_immutable_version(app, logged_in):
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.ai.capabilities import ToolContext
|
||||
from app.ai.tools import CAPABILITIES
|
||||
from app.business import Business
|
||||
|
||||
async with app.state.sessions.begin() as db:
|
||||
ctx = ToolContext(Business(db))
|
||||
content = template()
|
||||
content["variables"]["field"].pop("values")
|
||||
created = await CAPABILITIES["create_research_template"].invoke(ctx, content)
|
||||
content["description"] = "修订后的研究解释"
|
||||
args = {"asset_id": created["id"], "version": 1, "content": content}
|
||||
updated = await CAPABILITIES["create_research_template_version"].invoke(ctx, args)
|
||||
assert updated["version"] == 2
|
||||
with pytest.raises(HTTPException) as conflict:
|
||||
await CAPABILITIES["create_research_template_version"].invoke(ctx, args)
|
||||
assert conflict.value.status_code == 409
|
||||
old = (await logged_in.get(f"/api/v1/research/assets/{created['id']}?version=1")).json()
|
||||
assert old["content"]["description"] == "测试经济假设"
|
||||
|
||||
Reference in New Issue
Block a user