cleaned up function calling frames (#43)
This commit is contained in:
@@ -74,6 +74,9 @@ class UserStoppedSpeakingFrame(Frame):
|
||||
pass
|
||||
|
||||
@dataclass()
|
||||
class LLMFunctionStartFrame(Frame):
|
||||
function_name: str
|
||||
@dataclass()
|
||||
class LLMFunctionCallFrame(Frame):
|
||||
function_name: str
|
||||
arguments: str
|
||||
@@ -12,11 +12,11 @@ from dailyai.pipeline.frames import (
|
||||
LLMMessagesQueueFrame,
|
||||
LLMResponseEndFrame,
|
||||
LLMResponseStartFrame,
|
||||
LLMFunctionStartFrame,
|
||||
LLMFunctionCallFrame,
|
||||
Frame,
|
||||
TextFrame,
|
||||
TranscriptionQueueFrame,
|
||||
UserStoppedSpeakingFrame
|
||||
TranscriptionQueueFrame
|
||||
)
|
||||
|
||||
from abc import abstractmethod
|
||||
@@ -87,17 +87,24 @@ class LLMService(AIService):
|
||||
if isinstance(frame, LLMMessagesQueueFrame):
|
||||
yield LLMResponseStartFrame()
|
||||
async for text_chunk in self.run_llm_async(frame.messages, tool_choice):
|
||||
# We're streaming the LLM response and returning individual TextFrames for each chunk because
|
||||
# we want to enable quick TTS. But if the LLM response is a function call, we don't need to yield
|
||||
# each chunk because the function call is only useful as a single frame. Instead, we'll emit a
|
||||
# LLMFunctionStartFrame to let downstream services know a function call is coming, then we'll
|
||||
# collect the function arguments and return the entire call in a single LLMFunctionCallFrame.
|
||||
if isinstance(text_chunk, str):
|
||||
yield TextFrame(text_chunk)
|
||||
elif text_chunk.function:
|
||||
if text_chunk.function.name:
|
||||
# function_name += text_chunk.function.name
|
||||
yield LLMFunctionCallFrame(function_name=text_chunk.function.name, arguments=None)
|
||||
function_name += text_chunk.function.name
|
||||
yield LLMFunctionStartFrame(function_name=text_chunk.function.name)
|
||||
if text_chunk.function.arguments:
|
||||
# arguments += text_chunk.function.arguments
|
||||
yield LLMFunctionCallFrame(function_name=None, arguments=text_chunk.function.arguments)
|
||||
# Keep iterating through the response to collect all the argument fragments and
|
||||
# yield a complete LLMFunctionCallFrame after run_llm_async completes
|
||||
arguments += text_chunk.function.arguments
|
||||
|
||||
if (function_name and arguments):
|
||||
yield LLMFunctionCallFrame(function_name=function_name, arguments=arguments)
|
||||
function_name = ""
|
||||
arguments = ""
|
||||
yield LLMResponseEndFrame()
|
||||
|
||||
@@ -328,7 +328,7 @@ class BaseTransportService():
|
||||
break
|
||||
|
||||
def interrupt(self):
|
||||
self._logger.debug("!!! Interrupting")
|
||||
self._logger.debug("### Interrupting")
|
||||
self._is_interrupted.set()
|
||||
|
||||
async def get_receive_frames(self) -> AsyncGenerator[Frame, None]:
|
||||
|
||||
@@ -16,11 +16,11 @@ from dailyai.services.deepgram_ai_services import DeepgramTTSService
|
||||
from dailyai.services.elevenlabs_ai_service import ElevenLabsTTSService
|
||||
from dailyai.pipeline.aggregators import LLMAssistantContextAggregator, LLMContextAggregator, LLMUserContextAggregator, UserResponseAggregator, LLMResponseAggregator
|
||||
from support.runner import configure
|
||||
from dailyai.pipeline.frames import LLMMessagesQueueFrame, TranscriptionQueueFrame, Frame, TextFrame, LLMFunctionCallFrame, LLMResponseEndFrame, StartFrame, AudioFrame, SpriteFrame, ImageFrame
|
||||
from dailyai.pipeline.frames import LLMMessagesQueueFrame, TranscriptionQueueFrame, Frame, TextFrame, LLMFunctionCallFrame, LLMFunctionStartFrame, LLMResponseEndFrame, StartFrame, AudioFrame, SpriteFrame, ImageFrame
|
||||
from dailyai.services.ai_services import FrameLogger, AIService
|
||||
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
sounds = {}
|
||||
sound_files = [
|
||||
@@ -207,8 +207,6 @@ class ChecklistProcessor(AIService):
|
||||
self._messages = messages
|
||||
self._llm = llm
|
||||
self._tools = tools
|
||||
self._function_name = ""
|
||||
self._arguments = ""
|
||||
self._id = "You are Jessica, an agent for a company called Tri-County Health Services. Your job is to collect important information from the user before their doctor visit. You're talking to Chad Bailey. You should address the user by their first name and be polite and professional. You're not a medical professional, so you shouldn't provide any advice. Keep your responses short. Your job is to collect information to give to a doctor. Don't make assumptions about what values to plug into functions. Ask for clarification if a user response is ambiguous."
|
||||
self._acks = ["One sec.", "Let me confirm that.", "Thanks.", "OK."]
|
||||
|
||||
@@ -250,7 +248,7 @@ class ChecklistProcessor(AIService):
|
||||
# TODO-CB: forcing a global here :/
|
||||
self._tools.clear()
|
||||
self._tools.extend(this_step['tools'])
|
||||
if isinstance(frame, LLMFunctionCallFrame) and frame.function_name:
|
||||
if isinstance(frame, LLMFunctionStartFrame):
|
||||
print(f"... Preparing function call: {frame.function_name}")
|
||||
self._function_name = frame.function_name
|
||||
if this_step['run_async']:
|
||||
@@ -266,22 +264,19 @@ class ChecklistProcessor(AIService):
|
||||
# Insert a quick response while we run the function
|
||||
# yield AudioFrame(sounds["clack-short-quiet.wav"])
|
||||
pass
|
||||
elif isinstance(frame, LLMFunctionCallFrame) and frame.arguments:
|
||||
self._arguments += frame.arguments
|
||||
elif isinstance(frame, LLMResponseEndFrame):
|
||||
elif isinstance(frame, LLMFunctionCallFrame):
|
||||
|
||||
if self._function_name and self._arguments:
|
||||
if frame.function_name and frame.arguments:
|
||||
print(
|
||||
f"--> Calling function: {self._function_name} with arguments:")
|
||||
f"--> Calling function: {frame.function_name} with arguments:")
|
||||
pretty_json = re.sub("\n", "\n ", json.dumps(
|
||||
json.loads(self._arguments), indent=2))
|
||||
json.loads(frame.arguments), indent=2))
|
||||
print(f"--> {pretty_json}\n")
|
||||
if not self._function_name in self._functions:
|
||||
raise Exception(f"The LLM tried to call a function named {self._function_name}, which isn't in the list of known functions. Please check your prompt and/or self._functions.")
|
||||
fn = getattr(self, self._function_name)
|
||||
result = fn(json.loads(self._arguments))
|
||||
self._function_name = ""
|
||||
self._arguments = ""
|
||||
if not frame.function_name in self._functions:
|
||||
raise Exception(f"The LLM tried to call a function named {frame.function_name}, which isn't in the list of known functions. Please check your prompt and/or self._functions.")
|
||||
fn = getattr(self, frame.function_name)
|
||||
result = fn(json.loads(frame.arguments))
|
||||
|
||||
if not this_step['run_async']:
|
||||
if result:
|
||||
current_step += 1
|
||||
|
||||
Reference in New Issue
Block a user