Skip to content

Commit 0dfb971

Browse files
committed
Refactor OpenAI and Portkey clients to support new prompt formats and improve error handling
- Updated `OpenAIClient` and `PortkeyClient` to accept prompts as strings, dictionaries, or lists of dictionaries. - Enhanced completion methods to return empty strings for empty responses. - Improved error handling in `send_lm_request_batched` to provide clearer error messages. - Added assertions in `LMRequestHandler` to ensure prompts are not None. - Modified token tracking in `PortkeyClient` to handle optional usage attributes. - Updated various REPL environments to handle cases where no response is returned from the LM. - Enhanced logging to capture limit hit events and execution times. - Adjusted tests to reflect changes in prompt handling and response validation.
1 parent 5cf16d1 commit 0dfb971

24 files changed

Lines changed: 359 additions & 310 deletions

‎.github/workflows/docs.yml‎

Lines changed: 0 additions & 61 deletions
This file was deleted.

‎.github/workflows/publish.yml‎

Lines changed: 0 additions & 32 deletions
This file was deleted.

‎examples/bedrock_example.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
31
from dotenv import load_dotenv
42

53
from rlm import RLM

‎rlm/clients/anthropic.py‎

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def __init__(
1919
max_tokens: int = 32768,
2020
**kwargs,
2121
):
22-
super().__init__(model_name=model_name, **kwargs)
22+
super().__init__(model_name=model_name or "claude-3-5-sonnet-20241022", **kwargs)
2323
self.client = anthropic.Anthropic(api_key=api_key)
2424
self.async_client = anthropic.AsyncAnthropic(api_key=api_key)
2525
self.model_name = model_name
@@ -31,7 +31,9 @@ def __init__(
3131
self.model_output_tokens: dict[str, int] = defaultdict(int)
3232
self.model_total_tokens: dict[str, int] = defaultdict(int)
3333

34-
def completion(self, prompt: str | list[dict[str, Any]], model: str | None = None) -> str:
34+
def completion(
35+
self, prompt: str | dict[str, Any] | list[dict[str, Any]], model: str | None = None
36+
) -> str:
3537
messages, system = self._prepare_messages(prompt)
3638

3739
model = model or self.model_name
@@ -47,7 +49,7 @@ def completion(self, prompt: str | list[dict[str, Any]], model: str | None = Non
4749
return response.content[0].text
4850

4951
async def acompletion(
50-
self, prompt: str | list[dict[str, Any]], model: str | None = None
52+
self, prompt: str | dict[str, Any] | list[dict[str, Any]], model: str | None = None
5153
) -> str:
5254
messages, system = self._prepare_messages(prompt)
5355

@@ -64,21 +66,18 @@ async def acompletion(
6466
return response.content[0].text
6567

6668
def _prepare_messages(
67-
self, prompt: str | list[dict[str, Any]]
69+
self, prompt: str | dict[str, Any] | list[dict[str, Any]]
6870
) -> tuple[list[dict[str, Any]], str | None]:
6971
"""Prepare messages and extract system prompt for Anthropic API."""
7072
system = None
7173

7274
if isinstance(prompt, str):
7375
messages = [{"role": "user", "content": prompt}]
74-
elif isinstance(prompt, list) and all(isinstance(item, dict) for item in prompt):
75-
# Extract system message if present (Anthropic handles system separately)
76-
messages = []
77-
for msg in prompt:
78-
if msg.get("role") == "system":
79-
system = msg.get("content")
80-
else:
81-
messages.append(msg)
76+
elif isinstance(prompt, dict):
77+
# Single message dict - wrap in list
78+
messages = [prompt]
79+
elif isinstance(prompt, list):
80+
messages = prompt
8281
else:
8382
raise ValueError(f"Invalid prompt type: {type(prompt)}")
8483

‎rlm/clients/azure_openai.py‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(
2828
azure_deployment: str | None = None,
2929
**kwargs,
3030
):
31-
super().__init__(model_name=model_name, **kwargs)
31+
super().__init__(model_name=model_name or "gpt-4o", **kwargs)
3232

3333
if api_key is None:
3434
api_key = DEFAULT_AZURE_OPENAI_API_KEY
@@ -69,10 +69,14 @@ def __init__(
6969
self.model_output_tokens: dict[str, int] = defaultdict(int)
7070
self.model_total_tokens: dict[str, int] = defaultdict(int)
7171

72-
def completion(self, prompt: str | list[dict[str, Any]], model: str | None = None) -> str:
72+
def completion(
73+
self, prompt: str | dict[str, Any] | list[dict[str, Any]], model: str | None = None
74+
) -> str:
7375
if isinstance(prompt, str):
7476
messages = [{"role": "user", "content": prompt}]
75-
elif isinstance(prompt, list) and all(isinstance(item, dict) for item in prompt):
77+
elif isinstance(prompt, dict):
78+
messages = [prompt]
79+
elif isinstance(prompt, list):
7680
messages = prompt
7781
else:
7882
raise ValueError(f"Invalid prompt type: {type(prompt)}")
@@ -89,11 +93,13 @@ def completion(self, prompt: str | list[dict[str, Any]], model: str | None = Non
8993
return response.choices[0].message.content
9094

9195
async def acompletion(
92-
self, prompt: str | list[dict[str, Any]], model: str | None = None
96+
self, prompt: str | dict[str, Any] | list[dict[str, Any]], model: str | None = None
9397
) -> str:
9498
if isinstance(prompt, str):
9599
messages = [{"role": "user", "content": prompt}]
96-
elif isinstance(prompt, list) and all(isinstance(item, dict) for item in prompt):
100+
elif isinstance(prompt, dict):
101+
messages = [prompt]
102+
elif isinstance(prompt, list):
97103
messages = prompt
98104
else:
99105
raise ValueError(f"Invalid prompt type: {type(prompt)}")
@@ -109,7 +115,7 @@ async def acompletion(
109115
self._track_cost(response, model)
110116
return response.choices[0].message.content
111117

112-
def _track_cost(self, response: openai.ChatCompletion, model: str):
118+
def _track_cost(self, response: Any, model: str):
113119
self.model_call_counts[model] += 1
114120

115121
usage = getattr(response, "usage", None)

‎rlm/clients/base_lm.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ def __init__(self, model_name: str, **kwargs):
1515
self.kwargs = kwargs
1616

1717
@abstractmethod
18-
def completion(self, prompt: str | dict[str, Any]) -> str:
18+
def completion(self, prompt: str | dict[str, Any] | list[dict[str, Any]]) -> str:
1919
raise NotImplementedError
2020

2121
@abstractmethod
22-
async def acompletion(self, prompt: str | dict[str, Any]) -> str:
22+
async def acompletion(self, prompt: str | dict[str, Any] | list[dict[str, Any]]) -> str:
2323
raise NotImplementedError
2424

2525
@abstractmethod

‎rlm/clients/bedrock.py‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ def __init__(
7979
self.last_prompt_tokens = 0
8080
self.last_completion_tokens = 0
8181

82-
def completion(self, prompt: str | list[dict[str, Any]], model: str | None = None) -> str:
82+
def completion(
83+
self, prompt: str | dict[str, Any] | list[dict[str, Any]], model: str | None = None
84+
) -> str:
8385
"""Synchronous completion using Bedrock Converse API."""
8486
messages, system = self._prepare_messages(prompt)
8587

@@ -112,7 +114,7 @@ def completion(self, prompt: str | list[dict[str, Any]], model: str | None = Non
112114
return response["output"]["message"]["content"][0]["text"]
113115

114116
async def acompletion(
115-
self, prompt: str | list[dict[str, Any]], model: str | None = None
117+
self, prompt: str | dict[str, Any] | list[dict[str, Any]], model: str | None = None
116118
) -> str:
117119
"""
118120
Async completion using Bedrock Converse API.
@@ -125,7 +127,7 @@ async def acompletion(
125127
return self.completion(prompt, model)
126128

127129
def _prepare_messages(
128-
self, prompt: str | list[dict[str, Any]]
130+
self, prompt: str | dict[str, Any] | list[dict[str, Any]]
129131
) -> tuple[list[dict[str, Any]], str | None]:
130132
"""
131133
Prepare messages for Bedrock Converse API.
@@ -138,7 +140,18 @@ def _prepare_messages(
138140
if isinstance(prompt, str):
139141
# Simple string prompt
140142
messages = [{"role": "user", "content": [{"text": prompt}]}]
141-
elif isinstance(prompt, list) and all(isinstance(item, dict) for item in prompt):
143+
elif isinstance(prompt, dict):
144+
# Single message dict
145+
role = prompt.get("role")
146+
content = prompt.get("content", "")
147+
if role == "system":
148+
system = content
149+
messages = []
150+
elif role in ("user", "assistant"):
151+
messages = [{"role": role, "content": [{"text": content}]}]
152+
else:
153+
raise ValueError(f"Unsupported role: {role}")
154+
elif isinstance(prompt, list):
142155
# Message list format
143156
messages = []
144157
for msg in prompt:

‎rlm/clients/gemini.py‎

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def __init__(
2626
model_name: str | None = "gemini-2.5-flash",
2727
**kwargs,
2828
):
29-
super().__init__(model_name=model_name, **kwargs)
29+
super().__init__(model_name=model_name or "gemini-2.5-flash", **kwargs)
3030

3131
if api_key is None:
3232
api_key = DEFAULT_GEMINI_API_KEY
@@ -49,7 +49,9 @@ def __init__(
4949
self.last_prompt_tokens = 0
5050
self.last_completion_tokens = 0
5151

52-
def completion(self, prompt: str | list[dict[str, Any]], model: str | None = None) -> str:
52+
def completion(
53+
self, prompt: str | dict[str, Any] | list[dict[str, Any]], model: str | None = None
54+
) -> str:
5355
contents, system_instruction = self._prepare_contents(prompt)
5456

5557
model = model or self.model_name
@@ -67,10 +69,10 @@ def completion(self, prompt: str | list[dict[str, Any]], model: str | None = Non
6769
)
6870

6971
self._track_cost(response, model)
70-
return response.text
72+
return response.text or ""
7173

7274
async def acompletion(
73-
self, prompt: str | list[dict[str, Any]], model: str | None = None
75+
self, prompt: str | dict[str, Any] | list[dict[str, Any]], model: str | None = None
7476
) -> str:
7577
contents, system_instruction = self._prepare_contents(prompt)
7678

@@ -90,36 +92,49 @@ async def acompletion(
9092
)
9193

9294
self._track_cost(response, model)
93-
return response.text
95+
return response.text or ""
9496

9597
def _prepare_contents(
96-
self, prompt: str | list[dict[str, Any]]
98+
self, prompt: str | dict[str, Any] | list[dict[str, Any]]
9799
) -> tuple[list[types.Content] | str, str | None]:
98100
"""Prepare contents and extract system instruction for Gemini API."""
99-
system_instruction = None
100101

101102
if isinstance(prompt, str):
102103
return prompt, None
103104

104-
if isinstance(prompt, list) and all(isinstance(item, dict) for item in prompt):
105-
# Convert OpenAI-style messages to Gemini format
105+
if isinstance(prompt, dict):
106+
# Single message dict - wrap in list and process
107+
role = prompt.get("role")
108+
content = prompt.get("content", "")
109+
110+
if role == "system":
111+
# Gemini handles system instruction separately
112+
return [], content
113+
elif role == "user":
114+
return [types.Content(role="user", parts=[types.Part(text=content)])], None
115+
elif role == "assistant":
116+
# Gemini uses "model" instead of "assistant"
117+
return [types.Content(role="model", parts=[types.Part(text=content)])], None
118+
else:
119+
# Default to user role for unknown roles
120+
return [types.Content(role="user", parts=[types.Part(text=content)])], None
121+
122+
if isinstance(prompt, list):
123+
# List of message dicts - process all
106124
contents = []
125+
system_instruction = None
107126
for msg in prompt:
108127
role = msg.get("role")
109128
content = msg.get("content", "")
110129

111130
if role == "system":
112-
# Gemini handles system instruction separately
113131
system_instruction = content
114132
elif role == "user":
115133
contents.append(types.Content(role="user", parts=[types.Part(text=content)]))
116134
elif role == "assistant":
117-
# Gemini uses "model" instead of "assistant"
118135
contents.append(types.Content(role="model", parts=[types.Part(text=content)]))
119136
else:
120-
# Default to user role for unknown roles
121137
contents.append(types.Content(role="user", parts=[types.Part(text=content)]))
122-
123138
return contents, system_instruction
124139

125140
raise ValueError(f"Invalid prompt type: {type(prompt)}")

0 commit comments

Comments
 (0)