- ruff --fix 自动修正 44 项:移除未用 import(pytest 等)、import 块排序归一(I001) - 手工修复剩余 5 项:test_cascade.py 两处未用赋值(F841);test_s2_rules_and_logic.py 中部 import 移至文件顶部(E402 ×3) - 无行为变更:全量 492 项测试通过
149 lines
4.5 KiB
Python
149 lines
4.5 KiB
Python
"""Unit tests for agenteval.utils.llm — the shared LLM utility functions."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from agenteval.utils.llm import extract_content_from_llm_response, extract_reply_text, parse_json_from_llm_text
|
|
|
|
# ── extract_reply_text ───────────────────────────────────────────────────
|
|
|
|
def test_extract_reply_text_none():
|
|
assert extract_reply_text(None) == ""
|
|
|
|
|
|
def test_extract_reply_text_str():
|
|
assert extract_reply_text("hello") == "hello"
|
|
|
|
|
|
def test_extract_reply_text_dict_msgbody_dict():
|
|
msg = {"msgBody": {"content": "inner text"}}
|
|
assert extract_reply_text(msg) == "inner text"
|
|
|
|
|
|
def test_extract_reply_text_dict_msgbody_str():
|
|
msg = {"msgBody": "plain body"}
|
|
assert extract_reply_text(msg) == "plain body"
|
|
|
|
|
|
def test_extract_reply_text_dict_content_key():
|
|
msg = {"content": "direct content"}
|
|
assert extract_reply_text(msg) == "direct content"
|
|
|
|
|
|
def test_extract_reply_text_dict_msgbody_none_fallback_content():
|
|
# msgBody is falsy → falls back to content key
|
|
msg = {"msgBody": None, "content": "fallback"}
|
|
assert extract_reply_text(msg) == "fallback"
|
|
|
|
|
|
def test_extract_reply_text_int_coerces_to_str():
|
|
assert extract_reply_text(42) == "42"
|
|
|
|
|
|
def test_extract_reply_text_empty_dict():
|
|
assert extract_reply_text({}) == ""
|
|
|
|
|
|
# ── extract_content_from_llm_response ────────────────────────────────────
|
|
|
|
def test_extract_content_string_format():
|
|
data = {"choices": [{"message": {"content": "answer text"}}]}
|
|
assert extract_content_from_llm_response(data) == "answer text"
|
|
|
|
|
|
def test_extract_content_block_array_text():
|
|
data = {
|
|
"choices": [{
|
|
"message": {
|
|
"content": [
|
|
{"type": "text", "text": "block one"},
|
|
{"type": "text", "text": "block two"},
|
|
]
|
|
}
|
|
}]
|
|
}
|
|
result = extract_content_from_llm_response(data)
|
|
assert "block one" in result
|
|
assert "block two" in result
|
|
|
|
|
|
def test_extract_content_block_array_skips_non_text():
|
|
data = {
|
|
"choices": [{
|
|
"message": {
|
|
"content": [
|
|
{"type": "tool_use", "id": "t1", "input": {}},
|
|
{"type": "text", "text": "real answer"},
|
|
]
|
|
}
|
|
}]
|
|
}
|
|
result = extract_content_from_llm_response(data)
|
|
assert result == "real answer"
|
|
|
|
|
|
def test_extract_content_block_uses_content_key_fallback():
|
|
# Some providers use "content" instead of "text" inside blocks
|
|
data = {
|
|
"choices": [{
|
|
"message": {
|
|
"content": [{"type": "text", "content": "via content key"}]
|
|
}
|
|
}]
|
|
}
|
|
assert extract_content_from_llm_response(data) == "via content key"
|
|
|
|
|
|
def test_extract_content_missing_choices():
|
|
assert extract_content_from_llm_response({}) == ""
|
|
|
|
|
|
def test_extract_content_empty_choices():
|
|
assert extract_content_from_llm_response({"choices": []}) == ""
|
|
|
|
|
|
def test_extract_content_integer_coerced():
|
|
data = {"choices": [{"message": {"content": 123}}]}
|
|
assert extract_content_from_llm_response(data) == "123"
|
|
|
|
|
|
def test_extract_content_empty_block_list():
|
|
data = {"choices": [{"message": {"content": []}}]}
|
|
assert extract_content_from_llm_response(data) == ""
|
|
|
|
|
|
# ── parse_json_from_llm_text ─────────────────────────────────────────────
|
|
|
|
def test_parse_json_object():
|
|
result = parse_json_from_llm_text('{"score": 8, "reason": "good"}')
|
|
assert result["score"] == 8
|
|
|
|
|
|
def test_parse_json_array():
|
|
result = parse_json_from_llm_text('["a", "b", "c"]')
|
|
assert result == ["a", "b", "c"]
|
|
|
|
|
|
def test_parse_json_with_surrounding_text():
|
|
text = 'Here is the result: {"score": 7} and nothing else.'
|
|
result = parse_json_from_llm_text(text)
|
|
assert result["score"] == 7
|
|
|
|
|
|
def test_parse_json_array_with_preamble():
|
|
text = 'Generated questions: ["q1", "q2", "q3"]'
|
|
result = parse_json_from_llm_text(text)
|
|
assert result == ["q1", "q2", "q3"]
|
|
|
|
|
|
def test_parse_json_raises_on_no_json():
|
|
with pytest.raises((ValueError, json.JSONDecodeError)):
|
|
parse_json_from_llm_text("no json here at all")
|
|
|
|
|
|
def test_parse_json_markdown_wrapped():
|
|
text = '```json\n{"key": "value"}\n```'
|
|
# The fallback bracket-search finds the { in the markdown
|
|
result = parse_json_from_llm_text(text)
|
|
assert result["key"] == "value"
|