534bb94eea
Test Migrations / Migrations (SQLite) (push) Has been cancelled
Build Dev Image / build-dev-image (push) Has been cancelled
Check i18n Keys / Check i18n Key Consistency (push) Has been cancelled
Lint / Ruff Lint & Format (push) Has been cancelled
Lint / Frontend Lint (push) Has been cancelled
Test Migrations / Migrations (PostgreSQL) (push) Has been cancelled
94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
"""Unit tests for LiteLLMRequester._convert_messages.
|
|
|
|
Focus: the content-part normalization that (a) converts image_base64 parts to
|
|
the OpenAI image_url shape and (b) drops non-image file parts (file_base64 /
|
|
file_url) which OpenAI-compatible chat models reject. The latter is essential
|
|
for Voice/File attachments — including ones replayed from conversation history —
|
|
since the agent consumes their bytes via the sandbox, not the model payload.
|
|
"""
|
|
|
|
import langbot_plugin.api.entities.builtin.provider.message as provider_message
|
|
|
|
from langbot.pkg.provider.modelmgr.requesters.litellmchat import LiteLLMRequester
|
|
|
|
|
|
def _make_requester() -> LiteLLMRequester:
|
|
# _convert_messages does not touch instance config, so bypass __init__.
|
|
return LiteLLMRequester.__new__(LiteLLMRequester)
|
|
|
|
|
|
def test_convert_messages_drops_file_base64_part():
|
|
req = _make_requester()
|
|
msg = provider_message.Message(
|
|
role='user',
|
|
content=[
|
|
provider_message.ContentElement.from_text('analyze this audio'),
|
|
provider_message.ContentElement.from_file_base64('data:audio/wav;base64,AAAA', 'voice.wav'),
|
|
],
|
|
)
|
|
out = req._convert_messages([msg])
|
|
parts = out[0]['content']
|
|
types = [p.get('type') for p in parts]
|
|
assert 'file_base64' not in types
|
|
assert types == ['text']
|
|
assert parts[0]['text'] == 'analyze this audio'
|
|
|
|
|
|
def test_convert_messages_drops_file_url_part():
|
|
req = _make_requester()
|
|
msg = provider_message.Message(
|
|
role='user',
|
|
content=[
|
|
provider_message.ContentElement.from_text('here is a doc'),
|
|
provider_message.ContentElement.from_file_url('http://example.com/report.xlsx', 'report.xlsx'),
|
|
],
|
|
)
|
|
out = req._convert_messages([msg])
|
|
types = [p.get('type') for p in out[0]['content']]
|
|
assert types == ['text']
|
|
|
|
|
|
def test_convert_messages_keeps_image_and_converts_to_image_url():
|
|
req = _make_requester()
|
|
msg = provider_message.Message(
|
|
role='user',
|
|
content=[
|
|
provider_message.ContentElement.from_text('look'),
|
|
provider_message.ContentElement.from_image_base64('data:image/png;base64,AAAA'),
|
|
],
|
|
)
|
|
out = req._convert_messages([msg])
|
|
parts = out[0]['content']
|
|
types = [p.get('type') for p in parts]
|
|
# image is preserved and reshaped to the OpenAI image_url form
|
|
assert types == ['text', 'image_url']
|
|
img_part = parts[1]
|
|
assert img_part['image_url'] == {'url': 'data:image/png;base64,AAAA'}
|
|
assert 'image_base64' not in img_part
|
|
|
|
|
|
def test_convert_messages_mixed_history_strips_only_files():
|
|
req = _make_requester()
|
|
# Simulate replayed history: an old voice turn + a current text turn.
|
|
history_voice = provider_message.Message(
|
|
role='user',
|
|
content=[
|
|
provider_message.ContentElement.from_text('old audio turn'),
|
|
provider_message.ContentElement.from_file_base64('data:audio/wav;base64,BBBB', 'voice.wav'),
|
|
],
|
|
)
|
|
current = provider_message.Message(
|
|
role='user',
|
|
content=[provider_message.ContentElement.from_text('now do the csv')],
|
|
)
|
|
out = req._convert_messages([history_voice, current])
|
|
assert [p.get('type') for p in out[0]['content']] == ['text']
|
|
assert [p.get('type') for p in out[1]['content']] == ['text']
|
|
|
|
|
|
def test_convert_messages_plain_string_content_untouched():
|
|
req = _make_requester()
|
|
msg = provider_message.Message(role='user', content='just text')
|
|
out = req._convert_messages([msg])
|
|
assert out[0]['content'] == 'just text'
|