diff --git a/cookbook/agents/basic.py b/cookbook/agents/basic.py index 92a0a9594f..1f4439a7a4 100644 --- a/cookbook/agents/basic.py +++ b/cookbook/agents/basic.py @@ -1,7 +1,7 @@ import asyncio # noqa from typing import Iterator # noqa from rich.pretty import pprint # noqa -from phi.agent import Agent, AgentResponse # noqa +from phi.agent import Agent, RunResponse # noqa from phi.model.openai import OpenAIChat from phi.tools.yfinance import YFinanceTools from phi.storage.agent.postgres import PgAgentStorage @@ -16,7 +16,7 @@ storage=PgAgentStorage(table_name="agent_sessions", db_url="postgresql+psycopg://ai:ai@localhost:5532/ai"), ) -# run1: AgentResponse = agent.run("What is the stock price of NVDA") # type: ignore +# run1: RunResponse = agent.run("What is the stock price of NVDA") # type: ignore # pprint(run1) # print("------------*******************------------") # print(run) @@ -27,10 +27,10 @@ # print(m) # print("---") -# run: AgentResponse = agent.run("What is the stock price of NVDA") +# run: RunResponse = agent.run("What is the stock price of NVDA") # pprint(run.content) -run_stream: Iterator[AgentResponse] = agent.run( +run_stream: Iterator[RunResponse] = agent.run( "What is the stock price of NVDA", stream=True, stream_intermediate_steps=True ) for chunk in run_stream: @@ -40,7 +40,7 @@ # async def main(): -# run: AgentResponse = await agent.arun("What is the stock price of NVDA and TSLA") +# run: RunResponse = await agent.arun("What is the stock price of NVDA and TSLA") # pprint(run) # # async for chunk in await agent.arun("What is the stock price of NVDA and TSLA", stream=True): # # print(chunk.content) diff --git a/cookbook/agents/image.py b/cookbook/agents/image.py index 12220b50e2..78ccc36d09 100644 --- a/cookbook/agents/image.py +++ b/cookbook/agents/image.py @@ -1,5 +1,5 @@ from rich.pretty import pprint # noqa -from phi.agent import Agent, AgentResponse +from phi.agent import Agent, RunResponse from phi.model.openai import OpenAIChat agent = Agent( @@ -8,13 +8,13 @@ debug_mode=True, ) -# run: AgentResponse = agent.run( +# run: RunResponse = agent.run( # "What’s in this image?", # images=[ # "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" # ], # ) # type: ignore -# run: AgentResponse = agent.run( +# run: RunResponse = agent.run( # "What’s in this image?", # images=[ # { @@ -23,7 +23,7 @@ # } # ], # ) # type: ignore -run: AgentResponse = agent.run( +run: RunResponse = agent.run( "What are in these images? Is there any difference between them?", images=[ "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", diff --git a/cookbook/agents/intermediate_steps.py b/cookbook/agents/intermediate_steps.py index 91add897a6..990e59264f 100644 --- a/cookbook/agents/intermediate_steps.py +++ b/cookbook/agents/intermediate_steps.py @@ -1,6 +1,6 @@ from typing import Iterator from rich.pretty import pprint -from phi.agent import Agent, AgentResponse +from phi.agent import Agent, RunResponse from phi.model.openai import OpenAIChat from phi.tools.yfinance import YFinanceTools @@ -10,7 +10,7 @@ markdown=True, ) -run_stream: Iterator[AgentResponse] = agent.run( +run_stream: Iterator[RunResponse] = agent.run( "What is the stock price of NVDA", stream=True, stream_intermediate_steps=True ) for chunk in run_stream: diff --git a/cookbook/agents/knowledge.py b/cookbook/agents/knowledge.py index 707e46c69a..d18bdaf750 100644 --- a/cookbook/agents/knowledge.py +++ b/cookbook/agents/knowledge.py @@ -1,5 +1,5 @@ from rich.pretty import pprint # noqa -from phi.agent import Agent, AgentResponse # noqa +from phi.agent import Agent, RunResponse # noqa from phi.model.openai import OpenAIChat from phi.knowledge.pdf import PDFUrlKnowledgeBase from phi.vectordb.pgvector import PgVector, SearchType @@ -37,5 +37,5 @@ agent.print_response("How do i make Chicken and Galangal in Coconut Milk Soup") -# run1: AgentResponse = agent.run("How to make Gluai Buat Chi?") # type: ignore +# run1: RunResponse = agent.run("How to make Gluai Buat Chi?") # type: ignore # pprint(run1) diff --git a/cookbook/agents/structured_output.py b/cookbook/agents/structured_output.py index 34c6678cc6..b937d34223 100644 --- a/cookbook/agents/structured_output.py +++ b/cookbook/agents/structured_output.py @@ -9,7 +9,7 @@ from rich.text import Text from pydantic import BaseModel, Field -from phi.agent import Agent, AgentResponse +from phi.agent import Agent, RunResponse from phi.model.openai import OpenAIChat console = Console() @@ -80,7 +80,7 @@ def run_agents(): # Running movie_agent_1 display_header("Running Agent with response_model=MovieScript", panel_title="Agent 1") with console.status("Running Agent 1...", spinner="dots"): - run_movie_agent_1: AgentResponse = movie_agent_1.run("New York") + run_movie_agent_1: RunResponse = movie_agent_1.run("New York") display_content(run_movie_agent_1.content, title="Agent 1 Response") # Running movie_agent_2 @@ -88,7 +88,7 @@ def run_agents(): "Running Agent with response_model=MovieScript and structured_outputs=True", panel_title="Agent 2" ) with console.status("Running Agent 2...", spinner="dots"): - run_movie_agent_2: AgentResponse = movie_agent_2.run("New York") + run_movie_agent_2: RunResponse = movie_agent_2.run("New York") display_content(run_movie_agent_2.content, title="Agent 2 Response") except Exception as e: console.print(f"[bold red]Error occurred while running agents: {e}[/bold red]") @@ -99,7 +99,7 @@ async def run_agents_async(): # Running movie_agent_1 asynchronously display_header("Running Agent with response_model=MovieScript (async)", panel_title="Async Agent 1") with console.status("Running Agent 1...", spinner="dots"): - async_run_movie_agent_1: AgentResponse = await movie_agent_1.arun("New York") + async_run_movie_agent_1: RunResponse = await movie_agent_1.arun("New York") display_content(async_run_movie_agent_1.content, title="Async Agent 1 Response") # Running movie_agent_2 asynchronously @@ -108,7 +108,7 @@ async def run_agents_async(): panel_title="Async Agent 2", ) with console.status("Running Agent 2...", spinner="dots"): - async_run_movie_agent_2: AgentResponse = await movie_agent_2.arun("New York") + async_run_movie_agent_2: RunResponse = await movie_agent_2.arun("New York") display_content(async_run_movie_agent_2.content, title="Async Agent 2 Response") except Exception as e: console.print(f"[bold red]Error occurred while running async agents: {e}[/bold red]") diff --git a/cookbook/agents/telemetry.py b/cookbook/agents/telemetry.py index 94b0dec8de..fa3545d4d9 100644 --- a/cookbook/agents/telemetry.py +++ b/cookbook/agents/telemetry.py @@ -1,7 +1,7 @@ import asyncio # noqa from typing import Iterator # noqa from rich.pretty import pprint # noqa -from phi.agent import Agent, AgentResponse # noqa +from phi.agent import Agent, RunResponse # noqa from phi.model.openai import OpenAIChat from phi.tools.yfinance import YFinanceTools from phi.storage.agent.postgres import PgAgentStorage @@ -16,7 +16,7 @@ storage=PgAgentStorage(table_name="agent_sessions", db_url="postgresql+psycopg://ai:ai@localhost:5532/ai"), ) -# run1: AgentResponse = agent.run("What is the stock price of NVDA") # type: ignore +# run1: RunResponse = agent.run("What is the stock price of NVDA") # type: ignore # pprint(run1) # print("------------*******************------------") # print(run) @@ -27,10 +27,10 @@ # print(m) # print("---") -# run: AgentResponse = agent.run("What is the stock price of NVDA") +# run: RunResponse = agent.run("What is the stock price of NVDA") # pprint(run.content) -# run_stream: Iterator[AgentResponse] = agent.run( +# run_stream: Iterator[RunResponse] = agent.run( # "What is the stock price of NVDA", stream=True, stream_intermediate_steps=True # ) # for chunk in run_stream: @@ -41,7 +41,7 @@ async def main(): await agent.aprint_response("What is the stock price of NVDA and TSLA") - # run: AgentResponse = await agent.arun("What is the stock price of NVDA and TSLA") + # run: RunResponse = await agent.arun("What is the stock price of NVDA and TSLA") # pprint(run) # async for chunk in await agent.arun("What is the stock price of NVDA and TSLA", stream=True): # print(chunk.content) diff --git a/cookbook/providers/openai/agent.py b/cookbook/providers/openai/agent.py index b3cc9c763f..94ab2c2375 100644 --- a/cookbook/providers/openai/agent.py +++ b/cookbook/providers/openai/agent.py @@ -1,4 +1,4 @@ -from phi.agent import Agent, AgentResponse # noqa +from phi.agent import Agent, RunResponse # noqa from phi.model.openai import OpenAIChat from phi.tools.yfinance import YFinanceTools @@ -10,7 +10,7 @@ ) # Get the response in a variable -# run: AgentResponse = agent.run("What is the stock price of NVDA and TSLA") +# run: RunResponse = agent.run("What is the stock price of NVDA and TSLA") # print(run.content) # Print the response on the terminal diff --git a/cookbook/providers/openai/agent_stream.py b/cookbook/providers/openai/agent_stream.py index 2c019786fa..95fec0e9b5 100644 --- a/cookbook/providers/openai/agent_stream.py +++ b/cookbook/providers/openai/agent_stream.py @@ -1,5 +1,5 @@ from typing import Iterator # noqa -from phi.agent import Agent, AgentResponse # noqa +from phi.agent import Agent, RunResponse # noqa from phi.model.openai import OpenAIChat from phi.tools.yfinance import YFinanceTools @@ -11,7 +11,7 @@ ) # Get the response in a variable -# run_response: Iterator[AgentResponse] = agent.run("What is the stock price of NVDA and TSLA", stream=True) +# run_response: Iterator[RunResponse] = agent.run("What is the stock price of NVDA and TSLA", stream=True) # for chunk in run_response: # print(chunk.content) diff --git a/cookbook/providers/openai/basic.py b/cookbook/providers/openai/basic.py index af2b91f7e4..79e7c20dd2 100644 --- a/cookbook/providers/openai/basic.py +++ b/cookbook/providers/openai/basic.py @@ -1,10 +1,10 @@ -from phi.agent import Agent, AgentResponse # noqa +from phi.agent import Agent, RunResponse # noqa from phi.model.openai import OpenAIChat agent = Agent(model=OpenAIChat(id="gpt-4o"), instructions=["Respond in a southern tone"], markdown=True) # Get the response in a variable -# run: AgentResponse = agent.run("Explain simulation theory") +# run: RunResponse = agent.run("Explain simulation theory") # print(run.content) # Print the response on the terminal diff --git a/cookbook/providers/openai/basic_stream.py b/cookbook/providers/openai/basic_stream.py index 6528575b06..821eedd45d 100644 --- a/cookbook/providers/openai/basic_stream.py +++ b/cookbook/providers/openai/basic_stream.py @@ -1,11 +1,11 @@ from typing import Iterator # noqa -from phi.agent import Agent, AgentResponse # noqa +from phi.agent import Agent, RunResponse # noqa from phi.model.openai import OpenAIChat agent = Agent(model=OpenAIChat(id="gpt-4o"), instructions=["Respond in a southern tone"], markdown=True) # Get the response in a variable -# run_response: Iterator[AgentResponse] = agent.run("Explain simulation theory", stream=True) +# run_response: Iterator[RunResponse] = agent.run("Explain simulation theory", stream=True) # for chunk in run_response: # print(chunk.content) diff --git a/cookbook/providers/openai/structured_output.py b/cookbook/providers/openai/structured_output.py index b77359a159..93112953d2 100644 --- a/cookbook/providers/openai/structured_output.py +++ b/cookbook/providers/openai/structured_output.py @@ -1,7 +1,7 @@ from typing import List from rich.pretty import pprint # noqa from pydantic import BaseModel, Field -from phi.agent import Agent, AgentResponse # noqa +from phi.agent import Agent, RunResponse # noqa from phi.model.openai import OpenAIChat @@ -25,7 +25,7 @@ class MovieScript(BaseModel): ) # Get the response in a variable -# run: AgentResponse = movie_agent.run("New York") +# run: RunResponse = movie_agent.run("New York") # pprint(run.content) movie_writer.print_response("New York") diff --git a/cookbook/workflows/news_article.py b/cookbook/workflows/news_article.py index 44ce75a87f..37c64aee68 100644 --- a/cookbook/workflows/news_article.py +++ b/cookbook/workflows/news_article.py @@ -1,11 +1,13 @@ +from textwrap import dedent from typing import Optional, Iterator from pydantic import BaseModel, Field -from phi.agent import Agent, AgentResponse +from phi.agent import Agent, RunResponse from phi.workflow import Workflow from phi.tools.duckduckgo import DuckDuckGo -from phi.utils.pprint import pprint_agent_response_stream +from phi.tools.newspaper4k import Newspaper4k +from phi.utils.pprint import pprint_run_response from phi.utils.log import logger @@ -27,22 +29,63 @@ class WriteNewsReport(Workflow): response_model=NewsArticles, ) - writer: Agent = Agent() + writer: Agent = Agent( + name="Writer", + tools=[Newspaper4k()], + description="You are a Senior NYT Editor and your task is to write a NYT cover story due tomorrow.", + instructions=[ + "You will be provided with news articles and their links.", + "Carefully read each article and think about the contents", + "Then generate a final New York Times worthy article in the provided below.", + "Break the article into sections and provide key takeaways at the end.", + "Make sure the title is catchy and engaging.", + "Give the section relevant titles and provide details/facts/processes in each section." + "Ignore articles that you cannot read or understand.", + "REMEMBER: you are writing for the New York Times, so the quality of the article is important.", + ], + expected_output=dedent("""\ + An engaging, informative, and well-structured article in the following format: + + ## Engaging Article Title - def run(self, topic: str) -> Iterator[AgentResponse]: + ### Overview + {give a brief introduction of the article and why the user should read this report} + {make this section engaging and create a hook for the reader} + + ### Section 1 + {break the article into sections} + {provide details/facts/processes in this section} + + ... more sections as necessary... + + ### Takeaways + {provide key takeaways from the article} + + ### References + - [Title](url) + - [Title](url) + - [Title](url) + + """), + ) + + def run(self, topic: str) -> Iterator[RunResponse]: logger.info(f"Researching articles on: {topic}") - # research: AgentResponse = self.researcher.run(topic) - # if research.content and isinstance(research.content, NewsArticles) and research.content.articles: - # logger.info(f"Research identified {len(research.content.articles)} articles.") - # else: - # logger.error("No articles found.") - # return + research: RunResponse = self.researcher.run(topic) + if research and research.content and isinstance(research.content, NewsArticles) and research.content.articles: + logger.info(f"Researcher identified {len(research.content.articles)} articles.") + else: + yield RunResponse( + run_id=self.run_id, + content=f"Sorry could not find any articles on the topic: {topic}", + ) + return logger.info("Reading each article and writing a report.") - # writer.print_response(research.content.model_dump_json(indent=2), markdown=True, show_message=False) - yield from self.writer.run(f"write 1 sentence on {topic}", stream=True) + yield from self.writer.run(research.content.model_dump_json(indent=2), stream=True) # Run workflow -story = WriteNewsReport(debug_mode=False).run(topic="avocado toast") -pprint_agent_response_stream(story, markdown=True, show_time=True) +story = WriteNewsReport(debug_mode=False).run(topic="IBM Hashicorp Acquisition") +# Print the response +pprint_run_response(story, markdown=True, show_time=True) diff --git a/cookbook/workflows/news_article_old.py b/cookbook/workflows/news_article_old.py deleted file mode 100644 index 1d79f99315..0000000000 --- a/cookbook/workflows/news_article_old.py +++ /dev/null @@ -1,99 +0,0 @@ -# """ -# Please install dependencies using: -# pip install openai duckduckgo-search newspaper4k lxml_html_clean phidata -# """ -# -# from shutil import rmtree -# from pathlib import Path -# from textwrap import dedent -# from typing import Optional -# -# from pydantic import BaseModel, Field -# from phi.assistant import Assistant -# from phi.workflow import Workflow, Task -# from phi.tools.duckduckgo import DuckDuckGo -# from phi.tools.newspaper4k import Newspaper4k -# -# -# articles_dir = Path(__file__).parent.parent.parent.joinpath("wip", "articles") -# if articles_dir.exists(): -# rmtree(path=articles_dir, ignore_errors=True) -# articles_dir.mkdir(parents=True, exist_ok=True) -# -# -# class NewsArticle(BaseModel): -# title: str = Field(..., description="Title of the article.") -# url: str = Field(..., description="Link to the article.") -# summary: Optional[str] = Field(..., description="Summary of the article if available.") -# -# -# researcher = Assistant( -# name="Article Researcher", -# tools=[DuckDuckGo()], -# description="Given a topic, search for 15 articles and return the 7 most relevant articles.", -# output_model=NewsArticle, -# ) -# -# writer = Assistant( -# name="Article Writer", -# tools=[Newspaper4k()], -# description="You are a Senior NYT Editor and your task is to write a NYT cover story worthy article due tomorrow.", -# instructions=[ -# "You will be provided with news articles and their links.", -# "Carefully read each article and think about the contents", -# "Then generate a final New York Times worthy article in the provided below.", -# "Break the article into sections and provide key takeaways at the end.", -# "Make sure the title is catchy and engaging.", -# "Give the section relevant titles and provide details/facts/processes in each section." -# "Ignore articles that you cannot read or understand.", -# "REMEMBER: you are writing for the New York Times, so the quality of the article is important.", -# ], -# expected_output=dedent( -# """\ -# An engaging, informative, and well-structured article in the following format: -# -# ## Engaging Article Title -# -# ### Overview -# {give a brief introduction of the article and why the user should read this report} -# {make this section engaging and create a hook for the reader} -# -# ### Section 1 -# {break the article into sections} -# {provide details/facts/processes in this section} -# -# ... more sections as necessary... -# -# ### Takeaways -# {provide key takeaways from the article} -# -# ### References -# - [Title](url) -# - [Title](url) -# - [Title](url) -# -# """ -# ), -# ) -# -# news_article = Workflow( -# name="News Article Workflow", -# tasks=[ -# Task( -# description="Find the 7 most relevant articles on a topic.", -# assistant=researcher, -# show_output=False, -# ), -# Task( -# description="Read each article and and write a NYT worthy news article.", -# assistant=writer, -# ), -# ], -# debug_mode=True, -# save_output_to_file="news_article.md", -# ) -# -# news_article.print_response( -# "Hashicorp IBM acquisition", -# markdown=True, -# ) diff --git a/cookbook/workflows/news_article_wip.py b/cookbook/workflows/news_article_wip.py deleted file mode 100644 index 8fb26dcba5..0000000000 --- a/cookbook/workflows/news_article_wip.py +++ /dev/null @@ -1,87 +0,0 @@ -from textwrap import dedent -from typing import Optional - -from pydantic import BaseModel, Field - -from phi.agent import Agent, AgentResponse -from phi.model.openai import OpenAIChat -from phi.workflow import Workflow -from phi.tools.duckduckgo import DuckDuckGo -from phi.tools.newspaper4k import Newspaper4k -from phi.utils.log import logger - - -class NewsArticle(BaseModel): - title: str = Field(..., description="Title of the article.") - url: str = Field(..., description="Link to the article.") - summary: Optional[str] = Field(..., description="Summary of the article if available.") - - -class NewsArticles(BaseModel): - articles: list[NewsArticle] - - -class WriteNewsReport(Workflow): - researcher: Agent = Agent( - name="Researcher", - model=OpenAIChat(), - tools=[DuckDuckGo()], - description="Given a topic, search for 5 articles and return the 3 most relevant articles.", - response_model=NewsArticles, - ) - - writer: Agent = Agent( - name="Writer", - tools=[Newspaper4k()], - description="You are a Senior NYT Editor and your task is to write a NYT cover story due tomorrow.", - instructions=[ - "You will be provided with news articles and their links.", - "Carefully read each article and think about the contents", - "Then generate a final New York Times worthy article in the provided below.", - "Break the article into sections and provide key takeaways at the end.", - "Make sure the title is catchy and engaging.", - "Give the section relevant titles and provide details/facts/processes in each section." - "Ignore articles that you cannot read or understand.", - "REMEMBER: you are writing for the New York Times, so the quality of the article is important.", - ], - expected_output=dedent("""\ - An engaging, informative, and well-structured article in the following format: - - ## Engaging Article Title - - ### Overview - {give a brief introduction of the article and why the user should read this report} - {make this section engaging and create a hook for the reader} - - ### Section 1 - {break the article into sections} - {provide details/facts/processes in this section} - - ... more sections as necessary... - - ### Takeaways - {provide key takeaways from the article} - - ### References - - [Title](url) - - [Title](url) - - [Title](url) - - """), - ) - - def run(self, topic: str): - logger.info(f"Researching articles on: {topic}") - research: AgentResponse = self.researcher.run(topic) - if research.content and isinstance(research.content, NewsArticles) and research.content.articles: - logger.info(f"Research identified {len(research.content.articles)} articles.") - else: - logger.error("No articles found.") - return - - logger.info("Reading each article and writing a report.") - # writer.print_response(research.content.model_dump_json(indent=2), markdown=True, show_message=False) - - -# Run workflow -WriteNewsReport(debug_mode=True).run(topic="IBM Hashicorp Acquisition") diff --git a/phi/agent/__init__.py b/phi/agent/__init__.py index e5d3b9c30f..80b47501f2 100644 --- a/phi/agent/__init__.py +++ b/phi/agent/__init__.py @@ -9,5 +9,6 @@ Tool, Toolkit, Message, - AgentResponse, + RunResponse, + RunEvent, ) diff --git a/phi/agent/agent.py b/phi/agent/agent.py index c9d6706bf8..9ad47045a0 100644 --- a/phi/agent/agent.py +++ b/phi/agent/agent.py @@ -23,7 +23,7 @@ from phi.document import Document from phi.agent.session import AgentSession -from phi.agent.response import AgentResponse, AgentEvent +from phi.run.response import RunResponse, RunEvent from phi.knowledge.agent import AgentKnowledge from phi.model import Model from phi.model.message import Message, MessageContext @@ -184,8 +184,11 @@ class Agent(BaseModel): # Use the structured_outputs from the Model if available structured_outputs: bool = False - # -*- Final Agent Run Response - run_response: Optional[AgentResponse] = None + # -*- Agent run details + # Run ID: do not set manually + run_id: Optional[str] = None + # Response from the Agent run + run_response: Optional[RunResponse] = None # Save the response to a file save_response_to_file: Optional[str] = None @@ -909,7 +912,7 @@ def _run( messages: Optional[List[Union[Dict, Message]]] = None, stream_intermediate_steps: bool = False, **kwargs: Any, - ) -> Iterator[AgentResponse]: + ) -> Iterator[RunResponse]: """Run the Agent with a message and return the response. Steps: @@ -929,7 +932,8 @@ def _run( stream_agent_response = stream and self.streamable stream_intermediate_steps = stream_intermediate_steps and stream_agent_response # Create the run_response object - self.run_response = AgentResponse(run_id=str(uuid4()), agent_id=self.agent_id, session_id=self.session_id) + self.run_id = str(uuid4()) + self.run_response = RunResponse(run_id=self.run_id) logger.debug(f"*********** Agent Run Start: {self.run_response.run_id} ***********") @@ -937,12 +941,10 @@ def _run( self.update_model() self.run_response.model = self.model.id if self.model is not None else None if stream_intermediate_steps: - yield AgentResponse( - run_id=self.run_response.run_id, - agent_id=self.run_response.agent_id, - session_id=self.run_response.session_id, + yield RunResponse( + run_id=self.run_id, content="Run started", - event=AgentEvent.run_started.value, + event=RunEvent.run_started.value, ) # 2. Read existing session from storage @@ -1017,12 +1019,10 @@ def _run( yield self.run_response elif model_response_chunk.event == ModelResponseEvent.tool_call.value: if stream_intermediate_steps: - yield AgentResponse( - run_id=self.run_response.run_id, - agent_id=self.run_response.agent_id, - session_id=self.run_response.session_id, + yield RunResponse( + run_id=self.run_id, content=model_response_chunk.content, - event=AgentEvent.tool_call.value, + event=RunEvent.tool_call.value, ) else: model_response = self.model.response(messages=messages_for_model) @@ -1038,12 +1038,10 @@ def _run( self.run_response.metrics = self.model.metrics if self.model else None # 5. Update Memory if stream_intermediate_steps: - yield AgentResponse( - run_id=self.run_response.run_id, - agent_id=self.run_response.agent_id, - session_id=self.run_response.session_id, + yield RunResponse( + run_id=self.run_id, content="Updating memory", - event=AgentEvent.updating_memory.value, + event=RunEvent.updating_memory.value, ) # Add the user message to the chat history if message is not None: @@ -1165,12 +1163,10 @@ def _run( logger.debug(f"*********** Agent Run End: {self.run_response.run_id} ***********") if stream_intermediate_steps: - yield AgentResponse( - run_id=self.run_response.run_id, - agent_id=self.run_response.agent_id, - session_id=self.run_response.session_id, + yield RunResponse( + run_id=self.run_id, content="Run completed", - event=AgentEvent.run_completed.value, + event=RunEvent.run_completed.value, ) # -*- Yield final response if not streaming so that run() can get the response @@ -1186,7 +1182,7 @@ def run( images: Optional[List[Union[str, Dict]]] = None, messages: Optional[List[Union[Dict, Message]]] = None, **kwargs: Any, - ) -> AgentResponse: ... + ) -> RunResponse: ... @overload def run( @@ -1198,7 +1194,7 @@ def run( messages: Optional[List[Union[Dict, Message]]] = None, stream_intermediate_steps: bool = False, **kwargs: Any, - ) -> Iterator[AgentResponse]: ... + ) -> Iterator[RunResponse]: ... def run( self, @@ -1209,14 +1205,14 @@ def run( messages: Optional[List[Union[Dict, Message]]] = None, stream_intermediate_steps: bool = False, **kwargs: Any, - ) -> Union[AgentResponse, Iterator[AgentResponse]]: + ) -> Union[RunResponse, Iterator[RunResponse]]: """Run the Agent with a message and return the response.""" # If a response_model is set, return the response as a structured output if self.response_model is not None and self.parse_response: # Set stream=False and run the agent logger.debug("Setting stream=False as response_model is set") - run_response: AgentResponse = next( + run_response: RunResponse = next( self._run( message=message, stream=False, @@ -1293,7 +1289,7 @@ async def _arun( messages: Optional[List[Union[Dict, Message]]] = None, stream_intermediate_steps: bool = False, **kwargs: Any, - ) -> AsyncIterator[AgentResponse]: + ) -> AsyncIterator[RunResponse]: """Async Run the Agent with a message and return the response. Steps: @@ -1313,7 +1309,8 @@ async def _arun( stream_agent_response = stream and self.streamable stream_intermediate_steps = stream_intermediate_steps and stream_agent_response # Create the run_response object - self.run_response = AgentResponse(run_id=str(uuid4()), agent_id=self.agent_id, session_id=self.session_id) + self.run_id = str(uuid4()) + self.run_response = RunResponse(run_id=self.run_id) logger.debug(f"*********** Async Agent Run Start: {self.run_response.run_id} ***********") @@ -1321,12 +1318,10 @@ async def _arun( self.update_model() self.run_response.model = self.model.id if self.model is not None else None if stream_intermediate_steps: - yield AgentResponse( - run_id=self.run_response.run_id, - agent_id=self.run_response.agent_id, - session_id=self.run_response.session_id, + yield RunResponse( + run_id=self.run_id, content="Run started", - event=AgentEvent.run_started.value, + event=RunEvent.run_started.value, ) # 2. Read existing session from storage @@ -1402,12 +1397,10 @@ async def _arun( yield self.run_response elif model_response_chunk.event == ModelResponseEvent.tool_call.value: if stream_intermediate_steps: - yield AgentResponse( - run_id=self.run_response.run_id, - agent_id=self.run_response.agent_id, - session_id=self.run_response.session_id, + yield RunResponse( + run_id=self.run_id, content=model_response_chunk.content, - event=AgentEvent.tool_call.value, + event=RunEvent.tool_call.value, ) else: model_response = await self.model.aresponse(messages=messages_for_model) @@ -1423,12 +1416,10 @@ async def _arun( self.run_response.metrics = self.model.metrics if self.model else None # 5. Update Memory if stream_intermediate_steps: - yield AgentResponse( - run_id=self.run_response.run_id, - agent_id=self.run_response.agent_id, - session_id=self.run_response.session_id, + yield RunResponse( + run_id=self.run_id, content="Updating memory", - event=AgentEvent.updating_memory.value, + event=RunEvent.updating_memory.value, ) # Add the user message to the chat history if message is not None: @@ -1550,12 +1541,10 @@ async def _arun( logger.debug(f"*********** Async Agent Run End: {self.run_response.run_id} ***********") if stream_intermediate_steps: - yield AgentResponse( - run_id=self.run_response.run_id, - agent_id=self.run_response.agent_id, - session_id=self.run_response.session_id, + yield RunResponse( + run_id=self.run_id, content="Run completed", - event=AgentEvent.run_completed.value, + event=RunEvent.run_completed.value, ) # -*- Yield final response if not streaming so that run() can get the response @@ -1888,14 +1877,6 @@ def log_agent_run(self, run_id: str, run_data: Optional[Dict[str, Any]] = None) # Print Response ########################################################################### - def convert_response_to_string(self, response: Any) -> str: - if isinstance(response, str): - return response - elif isinstance(response, BaseModel): - return response.model_dump_json(exclude_none=True, indent=4) - else: - return json.dumps(response, indent=4) - def print_response( self, message: Optional[Union[List, Dict, str]] = None, @@ -1931,7 +1912,7 @@ def print_response( response_timer = Timer() response_timer.start() for resp in self.run(message=message, messages=messages, stream=True, **kwargs): - if isinstance(resp, AgentResponse) and isinstance(resp.content, str): + if isinstance(resp, RunResponse) and isinstance(resp.content, str): _response_content += resp.content response_content = Markdown(_response_content) if self.markdown else _response_content @@ -1954,12 +1935,12 @@ def print_response( response_timer.stop() response_content = "" - if isinstance(run_response, AgentResponse): + if isinstance(run_response, RunResponse): if isinstance(run_response.content, str): response_content = ( Markdown(run_response.content) if self.markdown - else self.convert_response_to_string(run_response.content) + else run_response.get_content_as_string(indent=4) ) elif self.response_model is not None and isinstance(run_response.content, BaseModel): try: @@ -2015,7 +1996,7 @@ async def aprint_response( response_timer = Timer() response_timer.start() async for resp in await self.arun(message=message, messages=messages, stream=True, **kwargs): # type: ignore #TODO: Review this - if isinstance(resp, AgentResponse) and isinstance(resp.content, str): + if isinstance(resp, RunResponse) and isinstance(resp.content, str): _response_content += resp.content response_content = Markdown(_response_content) if self.markdown else _response_content @@ -2038,12 +2019,12 @@ async def aprint_response( response_timer.stop() response_content = "" - if isinstance(run_response, AgentResponse): + if isinstance(run_response, RunResponse): if isinstance(run_response.content, str): response_content = ( Markdown(run_response.content) if self.markdown - else self.convert_response_to_string(run_response.content) + else run_response.get_content_as_string(indent=4) ) elif self.response_model is not None and isinstance(run_response.content, BaseModel): try: diff --git a/phi/playground/routes.py b/phi/playground/routes.py index 7c0a48b4ae..9b68f63bf3 100644 --- a/phi/playground/routes.py +++ b/phi/playground/routes.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, HTTPException, UploadFile from fastapi.responses import StreamingResponse, JSONResponse -from phi.agent.agent import Agent, AgentResponse +from phi.agent.agent import Agent, RunResponse from phi.agent.session import AgentSession from phi.playground.operator import format_tools, get_agent_by_id, get_session_title from phi.utils.log import logger @@ -59,7 +59,7 @@ def chat_response_streamer( ) -> Generator: run_response = agent.run(message, images=images, stream=True) for run_response_chunk in run_response: - run_response_chunk = cast(AgentResponse, run_response_chunk) + run_response_chunk = cast(RunResponse, run_response_chunk) yield run_response_chunk.model_dump_json() def process_image(file: UploadFile) -> List[Union[str, Dict]]: @@ -87,7 +87,7 @@ def agent_chat(body: AgentRunRequest): media_type="text/event-stream", ) else: - run_response = cast(AgentResponse, agent.run(body.message, images=base64_image, stream=False)) + run_response = cast(RunResponse, agent.run(body.message, images=base64_image, stream=False)) return run_response.model_dump_json() @playground_routes.post("/agent/sessions/all") diff --git a/phi/run/__init__.py b/phi/run/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/phi/agent/response.py b/phi/run/response.py similarity index 53% rename from phi/agent/response.py rename to phi/run/response.py index 1fc70f323f..d5739ae0b9 100644 --- a/phi/agent/response.py +++ b/phi/run/response.py @@ -7,30 +7,39 @@ from phi.model.message import Message, MessageContext -class AgentEvent(str, Enum): - """Events that can be sent by the Agent.run() method""" +class RunEvent(str, Enum): + """Events that can be sent by the run() functions""" run_started = "RunStarted" tool_call = "ToolCall" - agent_response = "AgentResponse" + agent_response = "RunResponse" updating_memory = "UpdatingMemory" run_completed = "RunCompleted" -class AgentResponse(BaseModel): - """Response returned by Agent.run()""" +class RunResponse(BaseModel): + """Response returned by Agent.run() or Workflow.run() functions""" - run_id: str - agent_id: str - session_id: str content: Optional[Any] = None content_type: str = "str" + context: Optional[List[MessageContext]] = None + event: str = RunEvent.agent_response.value + event_data: Optional[Dict[str, Any]] = None messages: Optional[List[Message]] = None metrics: Optional[Dict[str, Any]] = None - tools: Optional[List[Dict[str, Any]]] = None - context: Optional[List[MessageContext]] = None model: Optional[str] = None - event: str = AgentEvent.agent_response.value + run_id: str + tools: Optional[List[Dict[str, Any]]] = None created_at: int = Field(default_factory=lambda: int(time())) model_config = ConfigDict(arbitrary_types_allowed=True) + + def get_content_as_string(self, **kwargs) -> str: + import json + + if isinstance(self.content, str): + return self.content + elif isinstance(self.content, BaseModel): + return self.content.model_dump_json(exclude_none=True, **kwargs) + else: + return json.dumps(self.content, **kwargs) diff --git a/phi/task/__init__.py b/phi/task/__init__.py deleted file mode 100644 index d454c81292..0000000000 --- a/phi/task/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from phi.task.task import Task diff --git a/phi/task/task.py b/phi/task/task.py deleted file mode 100644 index 41446948d3..0000000000 --- a/phi/task/task.py +++ /dev/null @@ -1,112 +0,0 @@ -import json -from uuid import uuid4 -from typing import List, Any, Optional, Dict, Union, Iterator - -from pydantic import BaseModel, ConfigDict, field_validator, Field - -from phi.assistant import Assistant - - -class Task(BaseModel): - # -*- Task settings - # Task name - name: Optional[str] = None - # Task UUID (autogenerated if not set) - task_id: Optional[str] = Field(None, validate_default=True) - # Task description - description: Optional[str] = None - - # Assistant to run this task - assistant: Optional[Assistant] = None - # Reviewer for this task. Set reviewer=True for a default reviewer - reviewer: Optional[Union[Assistant, bool]] = None - - # -*- Task Output - # Final output of this Task - output: Optional[Any] = None - # If True, shows the output of the task in the workflow.run() function - show_output: bool = True - # Save the output to a file - save_output_to_file: Optional[str] = None - - # Cached values: do not set these directly - _assistant: Optional[Assistant] = None - - model_config = ConfigDict(arbitrary_types_allowed=True) - - @field_validator("task_id", mode="before") - def set_task_id(cls, v: Optional[str]) -> str: - return v if v is not None else str(uuid4()) - - @property - def streamable(self) -> bool: - return self.get_assistant().streamable - - def get_task_output_as_str(self) -> Optional[str]: - if self.output is None: - return None - - if isinstance(self.output, str): - return self.output - - if issubclass(self.output.__class__, BaseModel): - # Convert current_task_message to json if it is a BaseModel - return self.output.model_dump_json(exclude_none=True, indent=2) - - try: - return json.dumps(self.output, indent=2) - except Exception: - return str(self.output) - finally: - return None - - def get_assistant(self) -> Assistant: - if self._assistant is None: - self._assistant = self.assistant or Assistant() - return self._assistant - - def _run( - self, - message: Optional[Union[List, Dict, str]] = None, - *, - stream: bool = True, - **kwargs: Any, - ) -> Iterator[str]: - assistant = self.get_assistant() - assistant.task = self.description - - assistant_output = "" - if stream and self.streamable: - for chunk in assistant.run(message=message, stream=True, **kwargs): - assistant_output += chunk if isinstance(chunk, str) else "" - if self.show_output: - yield chunk if isinstance(chunk, str) else "" - else: - assistant_output = assistant.run(message=message, stream=False, **kwargs) # type: ignore - - self.output = assistant_output - if self.save_output_to_file: - fn = self.save_output_to_file.format(name=self.name, task_id=self.task_id) - with open(fn, "w") as f: - f.write(self.output) - - # -*- Yield task output if not streaming - if not stream: - if self.show_output: - yield self.output - else: - yield "" - - def run( - self, - message: Optional[Union[List, Dict, str]] = None, - *, - stream: bool = True, - **kwargs: Any, - ) -> Union[Iterator[str], str, BaseModel]: - if stream and self.streamable: - resp = self._run(message=message, stream=True, **kwargs) - return resp - else: - resp = self._run(message=message, stream=False, **kwargs) - return next(resp) diff --git a/phi/utils/pprint.py b/phi/utils/pprint.py index df77c16092..21c670eb2d 100644 --- a/phi/utils/pprint.py +++ b/phi/utils/pprint.py @@ -1,33 +1,61 @@ -from typing import Iterator +import json +from typing import Union, Iterable -from phi.agent.response import AgentResponse +from pydantic import BaseModel + +from phi.run.response import RunResponse from phi.utils.timer import Timer +from phi.utils.log import logger -def pprint_agent_response_stream( - response_stream: Iterator[AgentResponse], markdown: bool = False, show_time: bool = False +def pprint_run_response( + run_response: Union[RunResponse, Iterable[RunResponse]], markdown: bool = False, show_time: bool = False ) -> None: from rich.live import Live from rich.table import Table from rich.status import Status from rich.box import ROUNDED from rich.markdown import Markdown + from rich.json import JSON + from phi.cli.console import console + + # If run_response is a single RunResponse, wrap it in a list to make it iterable + if isinstance(run_response, RunResponse): + single_response_content: Union[str, JSON, Markdown] = "" + if isinstance(run_response.content, str): + single_response_content = ( + Markdown(run_response.content) if markdown else run_response.get_content_as_string(indent=4) + ) + elif isinstance(run_response.content, BaseModel): + try: + single_response_content = JSON(run_response.content.model_dump_json(exclude_none=True), indent=2) + except Exception as e: + logger.warning(f"Failed to convert response to Markdown: {e}") + else: + try: + single_response_content = JSON(json.dumps(run_response.content), indent=4) + except Exception as e: + logger.warning(f"Failed to convert response to string: {e}") - _response_content: str = "" - with Live() as live_log: - status = Status("Working...", spinner="dots") - live_log.update(status) - response_timer = Timer() - response_timer.start() - for resp in response_stream: - if isinstance(resp, AgentResponse) and isinstance(resp.content, str): - _response_content += resp.content - response_content = Markdown(_response_content) if markdown else _response_content + table = Table(box=ROUNDED, border_style="blue", show_header=False) + table.add_row(single_response_content) + console.print(table) + else: + streaming_response_content: str = "" + with Live() as live_log: + status = Status("Working...", spinner="dots") + live_log.update(status) + response_timer = Timer() + response_timer.start() + for resp in run_response: + if isinstance(resp, RunResponse) and isinstance(resp.content, str): + streaming_response_content += resp.content - table = Table(box=ROUNDED, border_style="blue", show_header=False) - if show_time: - table.add_row(f"Response\n({response_timer.elapsed:.1f}s)", response_content) # type: ignore - else: - table.add_row(response_content) # type: ignore - live_log.update(table) - response_timer.stop() + formatted_response = Markdown(streaming_response_content) if markdown else streaming_response_content # type: ignore + table = Table(box=ROUNDED, border_style="blue", show_header=False) + if show_time: + table.add_row(f"Response\n({response_timer.elapsed:.1f}s)", formatted_response) # type: ignore + else: + table.add_row(formatted_response) # type: ignore + live_log.update(table) + response_timer.stop() diff --git a/phi/workflow/response.py b/phi/workflow/response.py deleted file mode 100644 index afd0de4a7b..0000000000 --- a/phi/workflow/response.py +++ /dev/null @@ -1,27 +0,0 @@ -from time import time -from enum import Enum -from typing import Optional, Any, Dict - -from pydantic import BaseModel, ConfigDict, Field - - -class WorkflowEvent(str, Enum): - """Events that can be sent by Workflow.run()""" - - run_started = "RunStarted" - agent_response = "AgentResponse" - run_completed = "RunCompleted" - - -class WorkflowResponse(BaseModel): - """Response returned by Workflow.run()""" - - run_id: str - workflow_id: str - content: Optional[Any] = None - content_type: str = "str" - event: str = WorkflowEvent.agent_response.value - event_data: Optional[Dict[str, Any]] = None - created_at: int = Field(default_factory=lambda: int(time())) - - model_config = ConfigDict(arbitrary_types_allowed=True) diff --git a/phi/workflow/workflow.py b/phi/workflow/workflow.py index 8c42224274..cda0313e01 100644 --- a/phi/workflow/workflow.py +++ b/phi/workflow/workflow.py @@ -4,7 +4,6 @@ from pydantic import BaseModel, Field, ConfigDict, field_validator, PrivateAttr -from phi.workflow.response import WorkflowResponse from phi.utils.log import logger, set_log_level_to_debug @@ -24,10 +23,9 @@ class Workflow(BaseModel): # WorkflowSession from the database: DO NOT SET MANUALLY workflow_session: Optional[Any] = None - # -*- Final Workflow Run Response - run_response: Optional[WorkflowResponse] = None - # Save the response to a file - save_response_to_file: Optional[str] = None + # -*- Workflow run details + # Run ID: do not set manually + run_id: Optional[str] = None # debug_mode=True enables debug logs debug_mode: bool = False @@ -54,7 +52,9 @@ def run(self, *args: Any, **kwargs: Any): return def run_workflow(self, *args: Any, **kwargs: Any): - logger.debug(f"Running workflow: {self.workflow_id}") + self.run_id = str(uuid4()) + + logger.debug(f"*********** Running Workflow: {self.run_id} ***********") result = self._subclass_run(*args, **kwargs) return result diff --git a/phi/workflow/workflow_old.py b/phi/workflow/workflow_old.py deleted file mode 100644 index da46266263..0000000000 --- a/phi/workflow/workflow_old.py +++ /dev/null @@ -1,214 +0,0 @@ -from uuid import uuid4 -from typing import List, Any, Optional, Dict, Iterator, Union - -from pydantic import BaseModel, ConfigDict, field_validator, Field - -from phi.llm.base import LLM -from phi.task.task import Task -from phi.utils.log import logger, set_log_level_to_debug -from phi.utils.message import get_text_from_message -from phi.utils.timer import Timer - - -class Workflow(BaseModel): - # -*- Workflow settings - # LLM to use for this Workflow - llm: Optional[LLM] = None - # Workflow name - name: Optional[str] = None - - # -*- Run settings - # Run UUID (autogenerated if not set) - run_id: Optional[str] = Field(None, validate_default=True) - # Metadata associated with this run - run_data: Optional[Dict[str, Any]] = None - - # -*- User settings - # ID of the user running this workflow - user_id: Optional[str] = None - # Metadata associated the user running this workflow - user_data: Optional[Dict[str, Any]] = None - - # -*- Tasks in this workflow (required) - tasks: List[Task] - # Metadata associated with the assistant tasks - task_data: Optional[Dict[str, Any]] = None - - # -*- Workflow Output - # Final output of this Workflow - output: Optional[Any] = None - # Save the output to a file - save_output_to_file: Optional[str] = None - - # debug_mode=True enables debug logs - debug_mode: bool = False - # monitoring=True logs Workflow runs on phidata.app - monitoring: bool = False - - model_config = ConfigDict(arbitrary_types_allowed=True) - - @field_validator("debug_mode", mode="before") - def set_log_level(cls, v: bool) -> bool: - if v: - set_log_level_to_debug() - logger.debug("Debug logs enabled") - return v - - @field_validator("run_id", mode="before") - def set_run_id(cls, v: Optional[str]) -> str: - return v if v is not None else str(uuid4()) - - def _run( - self, - message: Optional[Union[List, Dict, str]] = None, - *, - stream: bool = True, - **kwargs: Any, - ) -> Iterator[str]: - logger.debug(f"*********** Workflow Run Start: {self.run_id} ***********") - - # List of tasks that have been run - executed_tasks: List[Task] = [] - workflow_output: List[str] = [] - - # -*- Generate response by running tasks - for idx, task in enumerate(self.tasks, start=1): - logger.debug(f"*********** Task {idx} Start ***********") - - # -*- Prepare input message for the current_task - task_input: List[str] = [] - if message is not None: - task_input.append(get_text_from_message(message)) - - if len(executed_tasks) > 0: - previous_task_outputs = [] - for previous_task_idx, previous_task in enumerate(executed_tasks, start=1): - previous_task_output = previous_task.get_task_output_as_str() - if previous_task_output is not None: - previous_task_outputs.append( - (previous_task_idx, previous_task.description, previous_task_output) - ) - - if len(previous_task_outputs) > 0: - task_input.append("\nHere are previous tasks and and their results:\n---") - for previous_task_idx, previous_task_description, previous_task_output in previous_task_outputs: - task_input.append(f"Task {previous_task_idx}: {previous_task_description}") - task_input.append(previous_task_output) - task_input.append("---") - - # -*- Run Task - task_output = "" - input_for_current_task = "\n".join(task_input) - if stream and task.streamable: - for chunk in task.run(message=input_for_current_task, stream=True, **kwargs): - task_output += chunk if isinstance(chunk, str) else "" - yield chunk if isinstance(chunk, str) else "" - else: - task_output = task.run(message=input_for_current_task, stream=False, **kwargs) # type: ignore - - executed_tasks.append(task) - workflow_output.append(task_output) - logger.debug(f"*********** Task {idx} End ***********") - if not stream: - yield task_output - - # -*- Save output to file if save_output_to_file is set - if self.save_output_to_file is not None: - try: - fn = self.save_output_to_file.format( - name=self.name, run_id=self.run_id, user_id=self.user_id, message=message - ) - with open(fn, "w") as f: - f.write("\n".join(workflow_output)) - except Exception as e: - logger.warning(f"Failed to save output to file: {e}") - - logger.debug(f"*********** Workflow Run End: {self.run_id} ***********") - - def run( - self, - message: Optional[Union[List, Dict, str]] = None, - *, - stream: bool = True, - **kwargs: Any, - ) -> Union[Iterator[str], str]: - if stream: - resp = self._run(message=message, stream=True, **kwargs) - return resp - else: - return "".join(self._run(message=message, stream=False, **kwargs)) - - def print_response( - self, - message: Optional[Union[List, Dict, str]] = None, - *, - stream: bool = True, - markdown: bool = False, - show_message: bool = True, - **kwargs: Any, - ) -> None: - from phi.cli.console import console - from rich.live import Live - from rich.table import Table - from rich.status import Status - from rich.progress import Progress, SpinnerColumn, TextColumn - from rich.box import ROUNDED - from rich.markdown import Markdown - - if stream: - response = "" - with Live() as live_log: - status = Status("Working...", spinner="dots") - live_log.update(status) - response_timer = Timer() - response_timer.start() - for resp in self.run(message=message, stream=True, **kwargs): - if isinstance(resp, str): - response += resp - _response = Markdown(response) if markdown else response - - table = Table(box=ROUNDED, border_style="blue", show_header=False) - if message and show_message: - table.show_header = True - table.add_column("Message") - table.add_column(get_text_from_message(message)) - table.add_row(f"Response\n({response_timer.elapsed:.1f}s)", _response) # type: ignore - live_log.update(table) - response_timer.stop() - else: - response_timer = Timer() - response_timer.start() - with Progress( - SpinnerColumn(spinner_name="dots"), TextColumn("{task.description}"), transient=True - ) as progress: - progress.add_task("Working...") - response = self.run(message=message, stream=False, **kwargs) # type: ignore - - response_timer.stop() - _response = Markdown(response) if markdown else response - - table = Table(box=ROUNDED, border_style="blue", show_header=False) - if message and show_message: - table.show_header = True - table.add_column("Message") - table.add_column(get_text_from_message(message)) - table.add_row(f"Response\n({response_timer.elapsed:.1f}s)", _response) # type: ignore - console.print(table) - - def cli_app( - self, - user: str = "User", - emoji: str = ":sunglasses:", - stream: bool = True, - markdown: bool = False, - exit_on: Optional[List[str]] = None, - ) -> None: - from rich.prompt import Prompt - - _exit_on = exit_on or ["exit", "quit", "bye"] - while True: - message = Prompt.ask(f"[bold] {emoji} {user} [/bold]") - if message in _exit_on: - break - - self.print_response(message=message, stream=stream, markdown=markdown)