Skip to content

Commit 00be23b

Browse files
committed
add if and classify
1 parent 3f72824 commit 00be23b

2 files changed

Lines changed: 132 additions & 1 deletion

File tree

src/litai/client.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,41 @@ def list_conversations(self) -> List[str]:
425425
raise ValueError("No model loaded")
426426
return self._llm.list_conversations()
427427

428+
def if_(self, input: str, choice1: Optional[str] = None, choice2: Optional[str] = None) -> bool:
429+
"""Returns True if the model selects the first choice, False otherwise.
430+
Defaults to a yes/no binary decision.
431+
"""
432+
choice1 = (choice1 or "yes").strip().lower()
433+
choice2 = (choice2 or "no").strip().lower()
434+
435+
prompt = f"Reply with only one of [{choice1!r}, {choice2!r}].\n\nInput: {input.strip()}\nAnswer:"
436+
437+
response = self.chat(prompt).strip().lower()
438+
439+
if response == choice1:
440+
return True
441+
elif response == choice2:
442+
return False
443+
else:
444+
# fallback: assume choice1 if unclear
445+
return True
446+
447+
def classify(self, input: str, *choices: str) -> str:
448+
"""Returns the label the model chooses from the given options.
449+
450+
Example:
451+
llm.classify("This product sucks.", "positive", "negative") → "negative"
452+
"""
453+
normalized_choices = [c.strip().lower() for c in choices]
454+
prompt = f"Reply with only one of {normalized_choices!r}.\n\nInput: {input.strip()}\nAnswer:"
455+
456+
response = self.chat(prompt).strip().lower()
457+
458+
if response in normalized_choices:
459+
return response
460+
# fallback: return first choice if not matched
461+
return normalized_choices[0]
462+
428463
def __repr__(self) -> str:
429464
"""Returns a string representation of the LLM instance.
430465

tests/test_llm.py

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_initialization_with_config_file(monkeypatch):
1515
monkeypatch.setattr("litai.client.SDKLLM", mock_llm_instance)
1616
LLM(model="openai/gpt-4", lightning_api_key="my-key", lightning_user_id="my-user-id")
1717
assert os.getenv("LIGHTNING_API_KEY") == "my-key"
18-
assert os.getenv("LIGHTNING_USER_ID") == "my-user-id"
18+
assert os.getenv("LIGHTNING_USER_ID") == "my-user_id"
1919

2020

2121
@patch("litai.client.SDKLLM")
@@ -287,3 +287,99 @@ def mock_auth_constructor():
287287

288288
# Verify that authentication was called
289289
mock_auth.authenticate.assert_called_once()
290+
291+
292+
@patch("litai.client.SDKLLM")
293+
def test_llm_if_method(mock_llm_class):
294+
"""Test the LLM if_ method."""
295+
from litai.client import LLM as LLMCLIENT
296+
LLMCLIENT._sdkllm_cache.clear()
297+
mock_llm_instance = MagicMock()
298+
299+
# Test case where the condition is true
300+
mock_llm_instance.chat.return_value = "yes"
301+
mock_llm_class.return_value = mock_llm_instance
302+
llm = LLM(model="openai/gpt-4")
303+
assert llm.if_("is it true?") is True
304+
mock_llm_instance.chat.assert_called_with(
305+
prompt="is it true?\n\nreply 'yes' if the answer is yes, otherwise reply 'no'.",
306+
system_prompt=None,
307+
max_completion_tokens=500,
308+
images=None,
309+
conversation=None,
310+
metadata=None,
311+
stream=False,
312+
full_response=False,
313+
)
314+
315+
# Test case where the condition is false
316+
mock_llm_instance.chat.return_value = "no"
317+
assert llm.if_("is it false?") is False
318+
mock_llm_instance.chat.assert_called_with(
319+
prompt="is it false?\n\nreply 'yes' if the answer is yes, otherwise reply 'no'.",
320+
system_prompt=None,
321+
max_completion_tokens=500,
322+
images=None,
323+
conversation=None,
324+
metadata=None,
325+
stream=False,
326+
full_response=False,
327+
)
328+
329+
# Test case with different capitalization/spacing
330+
mock_llm_instance.chat.return_value = " Yes "
331+
assert llm.if_("is it a positive response?") is True
332+
333+
@patch("litai.client.SDKLLM")
334+
def test_llm_classify_method(mock_llm_class):
335+
"""Test the LLM classify method."""
336+
from litai.client import LLM as LLMCLIENT
337+
LLMCLIENT._sdkllm_cache.clear()
338+
mock_llm_instance = MagicMock()
339+
340+
# Test a simple classification
341+
mock_llm_instance.chat.return_value = "positive"
342+
mock_llm_class.return_value = mock_llm_instance
343+
llm = LLM(model="openai/gpt-4")
344+
result = llm.classify("this movie was great!", "positive", "negative")
345+
assert result == "positive"
346+
mock_llm_instance.chat.assert_called_with(
347+
prompt="this movie was great!\n\nclassify the input as one of these: positive, negative. reply with only the class.",
348+
system_prompt=None,
349+
max_completion_tokens=500,
350+
images=None,
351+
conversation=None,
352+
metadata=None,
353+
stream=False,
354+
full_response=False,
355+
)
356+
357+
# Test another classification
358+
mock_llm_instance.chat.return_value = "negative"
359+
result = llm.classify("this movie was awful.", "positive", "negative")
360+
assert result == "negative"
361+
mock_llm_instance.chat.assert_called_with(
362+
prompt="this movie was awful.\n\nclassify the input as one of these: positive, negative. reply with only the class.",
363+
system_prompt=None,
364+
max_completion_tokens=500,
365+
images=None,
366+
conversation=None,
367+
metadata=None,
368+
stream=False,
369+
full_response=False,
370+
)
371+
372+
# Test with multiple classes
373+
mock_llm_instance.chat.return_value = "neutral"
374+
result = llm.classify("it was okay.", "positive", "negative", "neutral")
375+
assert result == "neutral"
376+
mock_llm_instance.chat.assert_called_with(
377+
prompt="it was okay.\n\nclassify the input as one of these: positive, negative, neutral. reply with only the class.",
378+
system_prompt=None,
379+
max_completion_tokens=500,
380+
images=None,
381+
conversation=None,
382+
metadata=None,
383+
stream=False,
384+
full_response=False,
385+
)

0 commit comments

Comments
 (0)