1
This commit is contained in:
parent
ad0247c2a8
commit
ce6a87fd19
@ -21,7 +21,7 @@ from app.db import chat_assistant_db
|
||||
from app.db.analytics import get_pipeline_runs
|
||||
from app.db.llm_insights import compute_input_hash, repair_mojibake_json, repair_mojibake_text
|
||||
from app.db.schema import get_conn
|
||||
from app.services.llm_insights import get_llm_params
|
||||
from app.services.llm_insights import _message_content_text, _parse_json_object_text, get_llm_params
|
||||
from app.services.market_overview import get_crypto_market_overview
|
||||
|
||||
|
||||
@ -616,11 +616,9 @@ def _call_chat_llm(message: str, context: dict, history=None) -> dict:
|
||||
if resp.status_code >= 400:
|
||||
return {"status": "failed", "error": f"http_{resp.status_code}:{resp.text[:300]}", "model": model}
|
||||
data = resp.json()
|
||||
content = (((data.get("choices") or [{}])[0]).get("message") or {}).get("content") or "{}"
|
||||
content = _message_content_text(((data.get("choices") or [{}])[0]).get("message") or {})
|
||||
content = repair_mojibake_text(content)
|
||||
parsed = repair_mojibake_json(json.loads(content))
|
||||
if not isinstance(parsed, dict):
|
||||
raise ValueError("llm_output_not_object")
|
||||
parsed = repair_mojibake_json(_parse_json_object_text(content))
|
||||
parsed.setdefault("summary", parsed.get("answer", "")[:80])
|
||||
parsed.setdefault("answer_style", _answer_style_for_intent(context.get("intent")))
|
||||
parsed.setdefault("evidence", [])
|
||||
|
||||
@ -76,6 +76,44 @@ def _parse_insight_payload(content):
|
||||
return content
|
||||
|
||||
|
||||
def _message_content_text(message: dict) -> str:
|
||||
content = (message or {}).get("content")
|
||||
if isinstance(content, str):
|
||||
return content.strip()
|
||||
if isinstance(content, list):
|
||||
parts = []
|
||||
for item in content:
|
||||
if isinstance(item, str):
|
||||
parts.append(item)
|
||||
elif isinstance(item, dict):
|
||||
parts.append(str(item.get("text") or item.get("content") or ""))
|
||||
return "\n".join(x for x in parts if x).strip()
|
||||
return ""
|
||||
|
||||
|
||||
def _parse_json_object_text(content: str) -> dict:
|
||||
text = str(content or "").strip()
|
||||
if not text:
|
||||
raise ValueError("llm_empty_content")
|
||||
if text.startswith("```"):
|
||||
text = text.strip("`").strip()
|
||||
if text.lower().startswith("json"):
|
||||
text = text[4:].strip()
|
||||
try:
|
||||
parsed = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
start = text.find("{")
|
||||
end = text.rfind("}")
|
||||
if start < 0 or end <= start:
|
||||
raise
|
||||
parsed = json.loads(text[start:end + 1])
|
||||
if not isinstance(parsed, dict):
|
||||
raise ValueError("llm_output_not_object")
|
||||
if not parsed:
|
||||
raise ValueError("llm_empty_json_object")
|
||||
return parsed
|
||||
|
||||
|
||||
def _call_llm_json(prompt_version, payload):
|
||||
params = get_llm_params()
|
||||
api_key = os.getenv(str(params.get("api_key_env") or "OPENAI_API_KEY"), "").strip()
|
||||
@ -113,10 +151,9 @@ def _call_llm_json(prompt_version, payload):
|
||||
if resp.status_code >= 400:
|
||||
return {"status": "failed", "error": f"http_{resp.status_code}", "raw": resp.text[:1000], "model": model}
|
||||
data = resp.json()
|
||||
content = (((data.get("choices") or [{}])[0]).get("message") or {}).get("content") or "{}"
|
||||
parsed = json.loads(content)
|
||||
if not isinstance(parsed, dict):
|
||||
raise ValueError("llm_output_not_object")
|
||||
message = (((data.get("choices") or [{}])[0]).get("message") or {})
|
||||
content = _message_content_text(message)
|
||||
parsed = _parse_json_object_text(content)
|
||||
return {"status": "success", "content": parsed, "model": model}
|
||||
except json.JSONDecodeError as exc:
|
||||
return {"status": "failed", "error": f"invalid_json:{exc}", "model": model}
|
||||
|
||||
@ -125,6 +125,60 @@ def test_invalid_json_is_marked_failed(monkeypatch, temp_db):
|
||||
assert row["status"] == "failed"
|
||||
|
||||
|
||||
def test_llm_empty_content_is_not_marked_success(monkeypatch):
|
||||
monkeypatch.setattr(llm_insights, "get_llm_params", lambda: {
|
||||
"enabled": True,
|
||||
"base_url": "https://llm.example/v1",
|
||||
"api_key_env": "TEST_LLM_KEY",
|
||||
"model": "deepseek-v4-pro",
|
||||
"timeout": 5,
|
||||
"max_tokens": 100,
|
||||
"modules": {},
|
||||
})
|
||||
monkeypatch.setenv("TEST_LLM_KEY", "test-key")
|
||||
|
||||
class Resp:
|
||||
status_code = 200
|
||||
text = "{}"
|
||||
|
||||
def json(self):
|
||||
return {"choices": [{"message": {"content": ""}}]}
|
||||
|
||||
monkeypatch.setattr(llm_insights.requests, "post", lambda *args, **kwargs: Resp())
|
||||
|
||||
result = llm_insights._call_llm_json("test_prompt", {"symbol": "AAA/USDT"})
|
||||
|
||||
assert result["status"] == "failed"
|
||||
assert "llm_empty_content" in result["error"]
|
||||
|
||||
|
||||
def test_llm_json_code_fence_is_parsed(monkeypatch):
|
||||
monkeypatch.setattr(llm_insights, "get_llm_params", lambda: {
|
||||
"enabled": True,
|
||||
"base_url": "https://llm.example/v1",
|
||||
"api_key_env": "TEST_LLM_KEY",
|
||||
"model": "deepseek-v4-pro",
|
||||
"timeout": 5,
|
||||
"max_tokens": 100,
|
||||
"modules": {},
|
||||
})
|
||||
monkeypatch.setenv("TEST_LLM_KEY", "test-key")
|
||||
|
||||
class Resp:
|
||||
status_code = 200
|
||||
text = "{}"
|
||||
|
||||
def json(self):
|
||||
return {"choices": [{"message": {"content": "```json\n{\"summary\":\"ok\"}\n```"}}]}
|
||||
|
||||
monkeypatch.setattr(llm_insights.requests, "post", lambda *args, **kwargs: Resp())
|
||||
|
||||
result = llm_insights._call_llm_json("test_prompt", {"symbol": "AAA/USDT"})
|
||||
|
||||
assert result["status"] == "success"
|
||||
assert result["content"]["summary"] == "ok"
|
||||
|
||||
|
||||
def test_only_key_samples_generate_insights(monkeypatch, temp_db):
|
||||
_insert_recommendation(temp_db, symbol="CCC/USDT", action_status="观察", execution_status="observe", display_bucket="watch_pool", state_reason="普通观察")
|
||||
_insert_recommendation(temp_db, symbol="DDD/USDT", action_status="等回踩", execution_status="wait_pullback", display_bucket="realtime", rec_time="2026-05-01T12:00:00")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user