-
Notifications
You must be signed in to change notification settings - Fork 72
Expand file tree
/
Copy pathlitellm_engine.py
More file actions
131 lines (104 loc) · 4.42 KB
/
Copy pathlitellm_engine.py
File metadata and controls
131 lines (104 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.
import logging
from typing import Any
from litellm import completion
from litellm.exceptions import APIError, AuthenticationError
from litellm.types.utils import Choices, ModelResponse
from pydantic import BaseModel
from are.simulation.agents.llm.llm_engine import LLMEngine, LLMEngineException
# TODO: Litellm should be agnostic to the agent or model. Remove this dependency.
from are.simulation.agents.llm.types import MessageRole
from are.simulation.agents.multimodal import Attachment
logger = logging.getLogger(__name__)
# TODO: Instead of tool, llama has ipython support. We should use that instead of user role.
role_conversions = {"tool-response": "user", "tool-call": "assistant"}
class LiteLLMModelConfig(BaseModel):
model_name: str
provider: str
endpoint: str | None = None
api_key: str | None = None
class LiteLLMEngine(LLMEngine):
"""
A class that extends the LLMEngine to provide a specific implementation for the Litellm model.
Attributes:
model_config (ModelConfig): The configuration for the model.
"""
def __init__(self, model_config: LiteLLMModelConfig):
super().__init__(model_config.model_name)
self.model_config = model_config
self.mock_response = None
if model_config.provider == "mock":
self.mock_response = """Thought: Good choice, this is a mock, so I can't do anything. Let's return the result.
Action:
{
"action": "_mock",
"action_input": "Mock result"
}<end_action>
"""
def _convert_message_to_litellm_format(
self, message: dict[str, Any]
) -> dict[str, Any]:
"""Convert a message to LiteLLM format, handling both text and multimodal content."""
role = MessageRole(message["role"]).value
role = role_conversions.get(role, role)
# Handle attachments if present
attachments: list[Attachment] | None = message.get("attachments")
content = message.get("content", "")
if attachments and len(attachments) > 0:
# Create multimodal content with both text and images
content_list = []
# Add text content if present
if content:
content_list.append({"type": "text", "text": content})
# Add image attachments
for attachment in attachments:
if attachment.mime.startswith("image/"):
content_list.append(attachment.to_openai_json())
else:
logger.warning(
f"Unsupported attachment mime type: {attachment.mime}"
)
return {"role": role, "content": content_list}
else:
# Text-only message
return {"role": role, "content": content}
def chat_completion(
self,
messages: list[dict[str, Any]],
stop_sequences=[],
**kwargs,
) -> tuple[str, dict | None]:
try:
# Convert messages to LiteLLM format with multimodal support
converted_messages = []
for message in messages:
converted_message = self._convert_message_to_litellm_format(message)
converted_messages.append(converted_message)
provider = (
self.model_config.provider
if self.model_config.provider != "local"
else "hosted_vllm"
)
response = completion(
model=self.model_config.model_name,
custom_llm_provider=provider,
messages=converted_messages,
api_base=self.model_config.endpoint,
api_key=self.model_config.api_key,
mock_response=self.mock_response,
)
assert type(response) is ModelResponse
assert len(response.choices) >= 1
assert type(response.choices[0]) is Choices
res = response.choices[0].message.content
assert res is not None
res = res.replace("False", "false").replace("True", "true")
for stop_token in stop_sequences:
res = res.split(stop_token)[0]
return res, None
except (AuthenticationError, APIError) as e:
raise LLMEngineException("Auth error in litellm.") from e