Skip to content

Commit

Permalink
v2.0.34
Browse files Browse the repository at this point in the history
  • Loading branch information
ashpreetbedi committed Nov 9, 2023
1 parent 1d42e2d commit 356aa54
Show file tree
Hide file tree
Showing 12 changed files with 836 additions and 47 deletions.
13 changes: 11 additions & 2 deletions phi/assistant/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ def create(self) -> "Assistant":
_file_ids = self.file_ids or []
if self.files is not None:
for _file in self.files:
_file_ids.append(_file.get_id())
_file = _file.get_or_create()
if _file.id is not None:
_file_ids.append(_file.id)
request_body["file_ids"] = _file_ids
if self.metadata is not None:
request_body["metadata"] = self.metadata
Expand Down Expand Up @@ -223,7 +225,13 @@ def update(self) -> "Assistant":
_file_ids = self.file_ids or []
if self.files is not None:
for _file in self.files:
_file_ids.append(_file.get_id())
try:
_file = _file.get()
if _file.id is not None:
_file_ids.append(_file.id)
except Exception as e:
logger.warning(f"Unable to get file: {e}")
continue
request_body["file_ids"] = _file_ids
if self.metadata:
request_body["metadata"] = self.metadata
Expand Down Expand Up @@ -267,6 +275,7 @@ def to_dict(self) -> Dict[str, Any]:
"tools",
"file_ids",
"files",
"created_at",
},
)

Expand Down
117 changes: 97 additions & 20 deletions phi/assistant/file/file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Optional
from typing import Any, Optional, Dict
from typing_extensions import Literal

from pydantic import BaseModel, ConfigDict
Expand Down Expand Up @@ -31,6 +31,10 @@ class File(BaseModel):
# Supported values are fine-tune, fine-tune-results, assistants, and assistants_output.
purpose: Literal["fine-tune", "assistants"] = "assistants"

# The current status of the file, which can be either `uploaded`, `processed`, or `error`.
status: Optional[Literal["uploaded", "processed", "error"]] = None
status_details: Optional[str] = None

# The Unix timestamp (in seconds) for when the file was created.
created_at: Optional[int] = None

Expand All @@ -46,48 +50,121 @@ def client(self) -> OpenAI:
def read(self) -> Any:
raise NotImplementedError

def write(self, content: Any) -> Any:
raise NotImplementedError

def get_filename(self) -> Optional[str]:
return self.filename

def load_from_storage(self):
pass

def load_from_openai(self, openai_file: OpenAIFile):
self.id = openai_file.id
self.object = openai_file.object
self.bytes = openai_file.bytes
self.created_at = openai_file.created_at
self.filename = openai_file.filename
self.status = openai_file.status
self.status_details = openai_file.status_details

def upload(self) -> OpenAIFile:
def create(self) -> "File":
self.openai_file = self.client.files.create(file=self.read(), purpose=self.purpose)
self.load_from_openai(self.openai_file)
logger.debug(f"File created: {self.openai_file.id}")
logger.debug(f"File: {self.openai_file}")
return self

def get_id(self) -> Optional[str]:
_id = self.id or self.openai_file.id if self.openai_file else None
if _id is None:
self.load_from_storage()
_id = self.id
return _id

def get_using_filename(self) -> Optional[OpenAIFile]:
file_list = self.client.files.list(purpose=self.purpose)
file_name = self.get_filename()
if file_name is None:
return None

logger.debug(f"Getting id for: {file_name}")
for file in file_list:
if file.filename == file_name:
logger.debug(f"Found: {file.id}")
return file
return None

def get_from_openai(self) -> OpenAIFile:
_file_id = self.get_id()
if _file_id is None:
oai_file = self.get_using_filename()
else:
oai_file = self.client.files.retrieve(file_id=_file_id)

if oai_file is None:
raise FileIdNotSet("File.id not set")

self.openai_file = oai_file
self.load_from_openai(self.openai_file)
return self.openai_file

def download(self, use_cache: bool = True) -> str:
def get(self, use_cache: bool = True) -> "File":
if self.openai_file is not None and use_cache:
return self

self.get_from_openai()
return self

def get_or_create(self, use_cache: bool = True) -> "File":
try:
file_to_download = self.get(use_cache=use_cache)
return self.get(use_cache=use_cache)
except FileIdNotSet:
return self.create()

def download(self):
try:
file_to_download = self.get_from_openai()
if file_to_download is not None:
content = self.client.files.retrieve_content(file_id=file_to_download.id)
return content
self.write(content)
except FileIdNotSet:
logger.warning("File not available")
raise

def get(self, use_cache: bool = True) -> OpenAIFile:
if self.openai_file is not None and use_cache:
return self.openai_file

_file_id = self.id or self.openai_file.id if self.openai_file else None
if _file_id is not None:
self.openai_file = self.client.files.retrieve(file_id=_file_id)
self.load_from_openai(self.openai_file)
return self.openai_file
raise FileIdNotSet("File.id not set")

def get_id(self) -> str:
return self.get().id

def delete(self) -> OpenAIFileDeleted:
try:
file_to_delete = self.get()
file_to_delete = self.get_from_openai()
if file_to_delete is not None:
deletion_status = self.client.files.delete(
file_id=file_to_delete.id,
)
logger.debug(f"File deleted: {file_to_delete.id}")
return deletion_status
except FileIdNotSet:
logger.warning("File not available")
raise

def to_dict(self) -> Dict[str, Any]:
return self.model_dump(
exclude_none=True,
include={
"filename",
"id",
"object",
"bytes",
"purpose",
"created_at",
},
)

def pprint(self):
"""Pretty print using rich"""
from rich.pretty import pprint

pprint(self.to_dict())

def __str__(self) -> str:
import json

return json.dumps(self.to_dict(), indent=4)
22 changes: 22 additions & 0 deletions phi/assistant/file/local.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from pathlib import Path
from typing import Any, Union, Optional

from phi.assistant.file import File
from phi.utils.log import logger


class LocalFile(File):
path: Union[str, Path]

@property
def filepath(self) -> Path:
if isinstance(self.path, str):
return Path(self.path)
return self.path

def read(self) -> Any:
logger.debug(f"Reading file: {self.filepath}")
return self.filepath.open("rb")

def get_filename(self) -> Optional[str]:
return self.filepath.name or self.filename
17 changes: 16 additions & 1 deletion phi/assistant/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def create(self, thread_id: Optional[str] = None) -> "Message":
_file_ids = self.file_ids or []
if self.files:
for _file in self.files:
_file_ids.append(_file.get_id())
_file = _file.get_or_create()
if _file.id is not None:
_file_ids.append(_file.id)
request_body["file_ids"] = _file_ids
if self.metadata is not None:
request_body["metadata"] = self.metadata
Expand Down Expand Up @@ -159,6 +161,19 @@ def update(self, thread_id: Optional[str] = None) -> "Message":
logger.warning("Message not available")
raise

def get_content_text(self) -> str:
if isinstance(self.content, str):
return self.content

content_str = ""
content_list = self.content or (self.openai_message.content if self.openai_message else None)
if content_list is not None:
for content in content_list:
if content.type == "text":
text = content.text
content_str += text.value
return content_str

def to_dict(self) -> Dict[str, Any]:
return self.model_dump(
exclude_none=True,
Expand Down
41 changes: 18 additions & 23 deletions phi/assistant/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,27 @@ def pprint(self):

pprint(self.to_dict())

def print_response(self, message: str, assistant: Assistant) -> None:
from phi.cli.console import console
def print_messages(self) -> None:
from rich.table import Table
from rich.box import ROUNDED
from rich.markdown import Markdown
from phi.cli.console import console

# Get the messages from the thread
messages = self.get_messages()

# Print the response
table = Table(box=ROUNDED, border_style="blue")
for m in messages[::-1]:
if m.role == "user":
table.add_column("User")
table.add_column(m.get_content_text())
if m.role == "assistant":
table.add_row("Assistant", Markdown(m.get_content_text()))
table.add_section()
console.print(table)

def print_response(self, message: str, assistant: Assistant) -> None:
# Start the response timer
response_timer = Timer()
response_timer.start()
Expand All @@ -227,27 +242,7 @@ def print_response(self, message: str, assistant: Assistant) -> None:
# Stop the response timer
response_timer.stop()

# Get the messages from the thread
messages = self.get_messages()

# Get the assistant response
assistant_response: str = ""
for m in messages:
oai_message = m.openai_message
if oai_message and oai_message.role == "assistant":
for content in oai_message.content:
if content.type == "text":
text = content.text
assistant_response += text.value
break

# Convert to markdown
md_response = Markdown(assistant_response)
table = Table(box=ROUNDED, border_style="blue")
table.add_column("Message")
table.add_column(message)
table.add_row(f"Response\n({response_timer.elapsed:.1f}s)", md_response)
console.print(table)
self.print_messages()

def __str__(self) -> str:
import json
Expand Down
53 changes: 53 additions & 0 deletions phi/assistant/tool/arxiv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import json
from typing import List, Optional

from phi.document import Document
from phi.knowledge.arxiv import ArxivKnowledgeBase
from phi.assistant.tool.registry import ToolRegistry
from phi.utils.log import logger


class ArxivTools(ToolRegistry):
def __init__(self, knowledge_base: Optional[ArxivKnowledgeBase] = None):
super().__init__(name="arxiv_tools")
self.knowledge_base: Optional[ArxivKnowledgeBase] = knowledge_base

if self.knowledge_base is not None and isinstance(self.knowledge_base, ArxivKnowledgeBase):
self.register(self.search_arxiv_and_update_knowledge_base)
else:
self.register(self.search_arxiv)

def search_arxiv_and_update_knowledge_base(self, topic: str) -> str:
"""This function searches arXiv for a topic, adds the results to the knowledge base and returns them.
USE THIS FUNCTION TO GET INFORMATION WHICH DOES NOT EXIST.
:param topic: The topic to search arXiv and add to knowledge base.
:return: Relevant documents from arXiv knowledge base.
"""
if self.knowledge_base is None:
return "Knowledge base not provided"

logger.debug(f"Adding to knowledge base: {topic}")
self.knowledge_base.queries.append(topic)
logger.debug("Loading knowledge base.")
self.knowledge_base.load(recreate=False)
logger.debug(f"Searching knowledge base: {topic}")
relevant_docs: List[Document] = self.knowledge_base.search(query=topic)
return json.dumps([doc.to_dict() for doc in relevant_docs])

def search_arxiv(self, query: str, max_results: int = 5) -> str:
"""
Searches arXiv for a query.
:param query: The query to search for.
:param max_results: The maximum number of results to return.
:return: Relevant documents from arXiv.
"""
from phi.document.reader.arxiv import ArxivReader

arxiv = ArxivReader(max_results=max_results)

logger.debug(f"Searching arxiv for: {query}")
relevant_docs: List[Document] = arxiv.read(query=query)
return json.dumps([doc.to_dict() for doc in relevant_docs])
Loading

0 comments on commit 356aa54

Please sign in to comment.