Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ To send a single query to an LLM, you can ask Neon to "ask Chat GPT <something>"
To start conversing with an LLM, ask to "talk to Chat GPT" and have all of your input
sent to an LLM until you say goodbye or stop talking for a while.

Supported LLMs (subject to what your Neon Diana/Hana backend has deployed):
Chat GPT, FastChat, Claude, and Gemini. Name any of them in a request,
e.g. "ask Claude <something>" or "talk to Gemini".

Enable fallback behavior by asking to "enable LLM fallback skill" or disable it
by asking to "disable LLM fallback".

Expand Down
39 changes: 34 additions & 5 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@
class LLM(Enum):
GPT = "Chat GPT"
FASTCHAT = "FastChat"
CLAUDE = "Claude"
GEMINI = "Gemini"


class LLMSkill(FallbackSkill):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.chat_history = dict()
self._default_user = "local"
self._default_llm = LLM.FASTCHAT
self.chatting = dict()
self.register_entity_file("llm.entity")

Expand All @@ -77,6 +78,23 @@ def chat_timeout_seconds(self):
def fallback_enabled(self):
return self.settings.get("fallback_enabled", False)

@property
def _default_llm(self) -> LLM:
"""LLM used for the fallback handler and unrecognized requests.

Configurable via the `default_llm` setting (spoken name or enum name,
e.g. "chat gpt", "Chat GPT", or "GPT"); defaults to FastChat.
"""
configured = self.settings.get("default_llm")
if not configured:
return LLM.FASTCHAT
requested = str(configured).strip().lower()
for llm in LLM:
if requested in (llm.value.lower(), llm.name.lower()):
return llm
LOG.warning(f"Unrecognized default_llm={configured!r}; using FastChat")
return LLM.FASTCHAT

@fallback_handler(85)
def fallback_llm(self, message):
if not self.fallback_enabled:
Expand All @@ -86,15 +104,17 @@ def fallback_llm(self, message):
LOG.info(f"Getting LLM response to: {utterance}")
user = get_message_user(message) or self._default_user

def _threaded_get_response(utt, usr):
def _threaded_get_response(utt, usr, message):
# `message` required to resolve response routing in `speak`
answer = self._get_llm_response(utt, usr, self._default_llm)
if not answer:
LOG.info("No fallback response")
return
self.speak(answer)

# TODO: Speak filler?
Thread(target=_threaded_get_response, args=(utterance, user), daemon=True).start()
Thread(target=_threaded_get_response,
args=(utterance, user, message), daemon=True).start()
return True

@intent_handler("enable_fallback.intent")
Expand Down Expand Up @@ -177,15 +197,20 @@ def _get_llm_response(self, query: str, user: str, llm: LLM) -> str:
endpoint = "chatgpt"
elif llm == LLM.FASTCHAT:
endpoint = "fastchat"
elif llm == LLM.CLAUDE:
endpoint = "claude"
elif llm == LLM.GEMINI:
endpoint = "gemini"
else:
raise ValueError(f"Expected LLM, got: {llm}")
self.chat_history.setdefault(user, list())
resp = request_backend(f"/llm/{endpoint}", {"query": query, "history": self.chat_history[user]})

resp = resp.get("response") or ""
if resp:
username = "user" if user == self._default_user else user
self.chat_history[user].append((username, query))
# History roles must be the literal "user"/"llm" the backend maps;
# the per-user key identifies the history, not the message role.
self.chat_history[user].append(("user", query))
self.chat_history[user].append(("llm", resp))
LOG.debug(f"Got LLM response: {resp}")
return resp
Expand All @@ -196,6 +221,10 @@ def _get_requested_llm(self, message: Message) -> LLM:
llm = LLM.GPT
elif self.voc_match(request, "fastchat"):
llm = LLM.FASTCHAT
elif self.voc_match(request, "claude"):
llm = LLM.CLAUDE
elif self.voc_match(request, "gemini"):
llm = LLM.GEMINI
else:
LOG.warning(f"No valid LLM in request: {request}")
llm = LLM.GPT
Expand Down
2 changes: 1 addition & 1 deletion locale/en-us/intent/ask_llm.intent
Original file line number Diff line number Diff line change
@@ -1 +1 @@
(ask|tell|task|half|sjet|asked|as) (chatgpt|chat gpt|chat g p t|chat gpg|chat gptc|jackgpt|jack gpt|jack chi pt|fast chat|fastchat|chat gbt|chatgbt|chat gpt4|chatgpt4|a language model|an llm) {question}
(ask|tell|task|half|sjet|asked|as) (chatgpt|chat gpt|chat g p t|chat gpg|chat gptc|jackgpt|jack gpt|jack chi pt|fast chat|fastchat|chat gbt|chatgbt|chat gpt4|chatgpt4|claude|anthropic|gemini|bard|a language model|an llm) {question}
2 changes: 1 addition & 1 deletion locale/en-us/intent/chat_with_llm.intent
Original file line number Diff line number Diff line change
@@ -1 +1 @@
(i want to |)(chat|talk|converse|start a conversation) (to|with) (chatgpt|chat gpt|chat g p t|chat gpg|chat gptc|jackgpt|jack gpt|jack chi pt|fast chat|fastchat|chat gbt|chatgbt|chat gpt4|chatgpt4|a language model|an llm)
(i want to |)(chat|talk|converse|start a conversation) (to|with) (chatgpt|chat gpt|chat g p t|chat gpg|chat gptc|jackgpt|jack gpt|jack chi pt|fast chat|fastchat|chat gbt|chatgbt|chat gpt4|chatgpt4|claude|anthropic|gemini|bard|a language model|an llm)
6 changes: 6 additions & 0 deletions locale/en-us/intent/llm.entity
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ chat gbt
chatgbt
chat gpt4
chatgpt4
fastchat
fast chat
claude
anthropic
gemini
bard
3 changes: 3 additions & 0 deletions locale/en-us/vocab/claude.voc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
claude
claud
anthropic
5 changes: 5 additions & 0 deletions locale/en-us/vocab/gemini.voc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
gemini
gemeni
bard
google's llm
google llm
2 changes: 1 addition & 1 deletion locale/uk-ua/intent/ask_llm.intent
Original file line number Diff line number Diff line change
@@ -1 +1 @@
(запитай|скажи|попроси|задай питання|запитати|спитай|дізнайся у) (chatgpt|chat gpt|chat g p t|chat gpg|chat gptc|jackgpt|jack gpt|чат gpt|чатgpt|штучний інтелект|мовну модель|llm) {question}
(запитай|скажи|попроси|задай питання|запитати|спитай|дізнайся у) (chatgpt|chat gpt|chat g p t|chat gpg|chat gptc|jackgpt|jack gpt|чат gpt|чатgpt|claude|клод|gemini|джеміні|bard|штучний інтелект|мовну модель|llm) {question}
2 changes: 1 addition & 1 deletion locale/uk-ua/intent/chat_with_llm.intent
Original file line number Diff line number Diff line change
@@ -1 +1 @@
(я хочу |)(поспілкуватися|поговорити|почати розмову|спілкуватися) (з|із) (chatgpt|chat gpt|chat g p t|chat gpg|chat gptc|jackgpt|jack gpt|чат gpt|чатgpt|штучним інтелектом|мовною моделлю|llm)
(я хочу |)(поспілкуватися|поговорити|почати розмову|спілкуватися) (з|із) (chatgpt|chat gpt|chat g p t|chat gpg|chat gptc|jackgpt|jack gpt|чат gpt|чатgpt|claude|клод|gemini|джеміні|bard|штучним інтелектом|мовною моделлю|llm)
6 changes: 6 additions & 0 deletions locale/uk-ua/intent/llm.entity
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ chatgpt4
чат gpt
штучний інтелект
мовна модель
claude
anthropic
клод
gemini
bard
джеміні
5 changes: 5 additions & 0 deletions locale/uk-ua/vocab/claude.voc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
claude
anthropic
клод
клауд
антропік
5 changes: 5 additions & 0 deletions locale/uk-ua/vocab/gemini.voc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
gemini
bard
джеміні
джемені
бард
2 changes: 2 additions & 0 deletions test/test_resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ vocab:
- exit
- chat_gpt
- fastchat
- claude
- gemini
# dialog is .dialog file basenames (case-sensitive)
dialog:
- end_chat
Expand Down
37 changes: 37 additions & 0 deletions test/test_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,43 @@ def test_handle_email_chat_history(self):

self.skill._send_email = real_send_email

def test_get_requested_llm(self):
cases = {
"ask chat gpt something": LLM.GPT,
"talk to fastchat": LLM.FASTCHAT,
"ask claude something": LLM.CLAUDE,
"chat with gemini": LLM.GEMINI,
}
for utterance, expected in cases.items():
message = Message("test", {"utterance": utterance})
self.assertEqual(self.skill._get_requested_llm(message), expected,
utterance)
# Unrecognized LLM falls back to GPT
message = Message("test", {"utterance": "ask the robot something"})
self.assertEqual(self.skill._get_requested_llm(message), LLM.GPT)

def test_get_llm_response_endpoints(self):
from unittest.mock import patch
expected = {
LLM.GPT: "chatgpt",
LLM.FASTCHAT: "fastchat",
LLM.CLAUDE: "claude",
LLM.GEMINI: "gemini",
}
real_history = self.skill.chat_history
try:
for llm, endpoint in expected.items():
self.skill.chat_history = dict()
with patch("skill_fallback_llm.request_backend",
return_value={"response": "hi"}) as mock_backend:
resp = self.skill._get_llm_response("hello", "endpoint_user",
llm)
self.assertEqual(resp, "hi")
self.assertEqual(mock_backend.call_args[0][0],
f"/llm/{endpoint}")
finally:
self.skill.chat_history = real_history

def test_converse(self):
# TODO
pass
Expand Down
Loading