fix(caching): keep structured function_call_output text in semantic prompts

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
kerry 2026-09-30 17:09:18 +00:00
parent 8b08e07f18
commit 8bf443997b
4 changed files with 39 additions and 4 deletions

View file

@ -199,10 +199,13 @@ fn collect_input_text(value: &Value, parts: &mut Vec<String>) {
return;
}
for key in ["text", "output", "input_text", "output_text"] {
if let Some(Value::String(text)) = map.get(key)
&& push_trimmed(text, parts)
{
return;
match map.get(key) {
Some(nested @ Value::Array(_)) => {
collect_input_text(nested, parts);
return;
}
Some(Value::String(text)) if push_trimmed(text, parts) => return,
_ => {}
}
}
}

View file

@ -240,6 +240,14 @@ fn prompt_from_messages_reads_messages_only(
])),
Some("update the config\n{\"name\":\"write_file\",\"arguments\":\"{\\\"path\\\":\\\"a.yaml\\\"}\"}\nok"),
)]
#[case::responses_structured_function_call_output(
None,
Some(json!([
{"type": "function_call", "call_id": "c1", "name": "write_file", "arguments": "{}"},
{"type": "function_call_output", "call_id": "c1", "output": [{"type": "input_text", "text": "denied"}]},
])),
Some("{\"name\":\"write_file\",\"arguments\":\"{}\"}\ndenied"),
)]
fn prompt_from_context_matches_python(
#[case] messages: Option<Value>,
#[case] input: Option<Value>,

View file

@ -299,6 +299,9 @@ class RedisSemanticCache(BaseCache):
for text_key in ("text", "output", "input_text", "output_text"):
text_value = value.get(text_key)
if isinstance(text_value, (list, tuple)):
cls._collect_responses_input_text(text_value, prompt_parts)
return
if isinstance(text_value, str):
stripped_text = text_value.strip()
if stripped_text:

View file

@ -614,6 +614,27 @@ def test_redis_semantic_cache_prompt_extraction_keeps_responses_function_calls()
assert prompt == 'update the config\n{"name":"write_file","arguments":"{\\"path\\":\\"a.yaml\\"}"}\nok'
def test_redis_semantic_cache_prompt_extraction_keeps_structured_function_call_outputs():
from litellm.caching.redis_semantic_cache import RedisSemanticCache
def prompt_for(output_text: str) -> str | None:
return RedisSemanticCache._get_prompt_from_kwargs(
input=[
{"type": "message", "role": "user", "content": [{"type": "input_text", "text": "write hello"}]},
{"type": "function_call", "call_id": "c1", "name": "write_file", "arguments": '{"path":"a.txt"}'},
{
"type": "function_call_output",
"call_id": "c1",
"output": [{"type": "input_text", "text": output_text}],
},
]
)
expected_call = '{"name":"write_file","arguments":"{\\"path\\":\\"a.txt\\"}"}'
assert prompt_for("wrote 5 bytes") == f"write hello\n{expected_call}\nwrote 5 bytes"
assert prompt_for("wrote 5 bytes") != prompt_for("PermissionError")
def test_redis_semantic_cache_prompt_extraction_handles_model_objects():
from litellm.caching.redis_semantic_cache import RedisSemanticCache