├── requirements.txt ├── .gitignore ├── example.env ├── README.md ├── main.py ├── assistant_manager.py ├── thread_manager.py └── chat_session.py /requirements.txt: -------------------------------------------------------------------------------- 1 | openai 2 | python-dotenv 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | /venv 3 | .idea 4 | .env 5 | models 6 | /models 7 | __pycache__ 8 | data.json -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=your-api-key 2 | ASSISTANT_ID=your-assistant-id 3 | ASSISTANT_NAME=your-assistant-name 4 | FILE_IDS=id1,id2,id3 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Openai Assistant - A simple assistant for Example of OpenAI assistant 2 | 3 | ## Overview 4 | here I show a basic example of how to use the OpenAI assistant API to create a simple assistant. and do conversation with that and create multiple threads. 5 | 6 | ## Installation 7 | ```bash 8 | git clone https://github.com/shamspias/openai-assistent-python 9 | ``` 10 | ```bash 11 | python3 -m venv venv 12 | . ven/bin/activate 13 | ```` 14 | ```bash 15 | pip install -r requirements.txt 16 | ``` 17 | 18 | # Environment Variables 19 | ```bash 20 | cp example.env .env 21 | ``` 22 | Your `ASSISTANT_ID` or `ASSISTANT_NAME` and `API_KEY` in `.env` file 23 | 24 | ```bash 25 | python3 main.py 26 | ``` 27 | 28 | ## Usage 29 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | from dotenv import load_dotenv 4 | from openai import AsyncOpenAI 5 | from thread_manager import ThreadManager 6 | from assistant_manager import AssistantManager 7 | from chat_session import ChatSession 8 | 9 | # Load environment variables from .env file 10 | load_dotenv() 11 | 12 | # Use the environment variables 13 | API_KEY = os.getenv('API_KEY') 14 | ASSISTANT_NAME = os.getenv('ASSISTANT_NAME') 15 | ASSISTANT_ID = os.getenv('ASSISTANT_ID') 16 | MODEL_NAME = os.getenv('MODEL_NAME') 17 | 18 | 19 | # ASSISTANT_ID will be fetched dynamically after creation or during the session 20 | 21 | # Entry point 22 | async def main(): 23 | # Initialize the OpenAI async client 24 | openai_async_client = AsyncOpenAI(api_key=API_KEY) 25 | 26 | # Create instances of manager classes with the OpenAI client 27 | thread_manager = ThreadManager(openai_async_client) 28 | assistant_manager = AssistantManager(openai_async_client) 29 | 30 | # Start a chat session with the assistant name and model name 31 | chat_session = ChatSession(thread_manager, assistant_manager, ASSISTANT_NAME, MODEL_NAME, ASSISTANT_ID) 32 | await chat_session.start_session() 33 | 34 | 35 | if __name__ == "__main__": 36 | asyncio.run(main()) 37 | -------------------------------------------------------------------------------- /assistant_manager.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | 4 | class AssistantManager: 5 | """ 6 | A class to manage assistants. 7 | """ 8 | def __init__(self, client): 9 | self.client = client 10 | 11 | async def list_assistants(self): 12 | response = await self.client.beta.assistants.list() 13 | return {assistant.name: assistant.id for assistant in response.data} 14 | 15 | async def retrieve_assistant(self, assistant_id: str): 16 | return await self.client.beta.assistants.retrieve(assistant_id) 17 | 18 | async def create_assistant(self, name: str, instructions: str, tools: list, model: str): 19 | return await self.client.beta.assistants.create( 20 | name=name, 21 | instructions=instructions, 22 | tools=tools, 23 | model=model 24 | ) 25 | 26 | async def update_assistant(self, assistant_id: str, name: Optional[str] = None, description: Optional[str] = None, 27 | instructions: Optional[str] = None, tools: Optional[list] = None): 28 | update_fields = {} 29 | if name is not None: 30 | update_fields['name'] = name 31 | if description is not None: 32 | update_fields['description'] = description 33 | if instructions is not None: 34 | update_fields['instructions'] = instructions 35 | if tools is not None: 36 | update_fields['tools'] = tools 37 | return await self.client.beta.assistants.update(assistant_id, **update_fields) 38 | 39 | async def delete_assistant(self, assistant_id: str): 40 | return await self.client.beta.assistants.delete(assistant_id) 41 | 42 | async def create_assistant_file(self, assistant_id: str, file_id: str): 43 | return await self.client.beta.assistants.files.create(assistant_id=assistant_id, file_id=file_id) 44 | 45 | async def delete_assistant_file(self, assistant_id: str, file_id: str): 46 | return await self.client.beta.assistants.files.delete(assistant_id, file_id) 47 | 48 | async def list_assistant_files(self, assistant_id: str): 49 | return await self.client.beta.assistants.files.list(assistant_id) 50 | 51 | async def get_assistant_id_by_name(self, name: str): 52 | """ 53 | Get the ID of an assistant by its name. 54 | 55 | Args: 56 | name (str): The name of the assistant. 57 | 58 | Returns: 59 | str: The ID of the assistant if found, otherwise None. 60 | """ 61 | assistants = await self.list_assistants() 62 | return assistants.get(name) 63 | -------------------------------------------------------------------------------- /thread_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import gpt3_tokenizer 4 | from typing import Optional 5 | 6 | 7 | class ThreadManager: 8 | def __init__(self, client): 9 | self.client = client 10 | 11 | async def list_messages(self, thread_id: str, limit: int = 20, order: str = 'desc', after: Optional[str] = None, 12 | before: Optional[str] = None): 13 | try: 14 | return await self.client.beta.threads.messages.list(thread_id=thread_id, 15 | limit=limit, 16 | order=order, 17 | after=after, 18 | before=before 19 | ) 20 | except Exception as e: 21 | print(f"An error occurred while retrieving messages: {e}") 22 | return None 23 | 24 | async def retrieve_message(self, thread_id: str, message_id: str): 25 | return await self.client.beta.threads.messages.retrieve(thread_id=thread_id, message_id=message_id) 26 | 27 | async def create_thread(self, messages: Optional[list] = None, metadata: Optional[dict] = None): 28 | return await self.client.beta.threads.create(messages=messages, metadata=metadata) 29 | 30 | async def retrieve_thread(self, thread_id: str): 31 | return await self.client.beta.threads.retrieve(thread_id) 32 | 33 | async def modify_thread(self, thread_id: str, metadata: dict): 34 | return await self.client.beta.threads.modify(thread_id, metadata=metadata) 35 | 36 | async def delete_thread(self, thread_id: str): 37 | return await self.client.beta.threads.delete(thread_id) 38 | 39 | async def send_message(self, thread_id: str, content: str, role: str = "user"): 40 | # token_count = self.count_tokens(content) 41 | # print(f"Tokens used in message: {token_count}") 42 | return await self.client.beta.threads.messages.create(thread_id=thread_id, role=role, content=content) 43 | 44 | async def create_run(self, thread_id: str, assistant_id: str): 45 | return await self.client.beta.threads.runs.create(thread_id=thread_id, assistant_id=assistant_id) 46 | 47 | async def list_runs(self, thread_id: str): 48 | return await self.client.beta.threads.runs.list(thread_id=thread_id) 49 | 50 | @staticmethod 51 | def count_tokens(text: str): 52 | # Placeholder for the actual token counting logic 53 | return gpt3_tokenizer.count_tokens(text) 54 | 55 | @staticmethod 56 | def read_thread_data(filename: str = 'data.json'): 57 | if os.path.exists(filename): 58 | with open(filename, 'r') as file: 59 | return json.load(file) 60 | return {} 61 | 62 | @staticmethod 63 | def save_thread_data(thread_id: str, filename: str = 'data.json'): 64 | data = {'thread_id': thread_id} 65 | with open(filename, 'w') as file: 66 | json.dump(data, file) 67 | -------------------------------------------------------------------------------- /chat_session.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from thread_manager import ThreadManager 3 | from assistant_manager import AssistantManager 4 | 5 | 6 | class ChatSession: 7 | def __init__(self, thread_manager: ThreadManager, assistant_manager: AssistantManager, assistant_name: str, 8 | model_name: str, assistant_id: str = None, thread_id: str = None): 9 | self.thread_manager = thread_manager 10 | self.assistant_manager = assistant_manager 11 | self.assistant_name = assistant_name 12 | self.model_name = model_name 13 | self.assistant_id = assistant_id 14 | self.thread_id = thread_id 15 | 16 | async def start_session(self): 17 | if self.thread_id is None: 18 | # Get or create a thread 19 | self.thread_id = await self.get_or_create_thread() 20 | 21 | if self.assistant_id is None: 22 | # Find or create an assistant 23 | self.assistant_id = await self.find_or_create_assistant( 24 | name=self.assistant_name, 25 | model=self.model_name 26 | ) 27 | 28 | # Display existing chat history 29 | await self.display_chat_history() 30 | 31 | prev_messages = await self.thread_manager.list_messages(self.thread_id) 32 | if prev_messages is None: 33 | print("An error occurred while retrieving messages.") 34 | return 35 | 36 | # Start the chat loop 37 | await self.chat_loop() 38 | 39 | async def chat_loop(self): 40 | try: 41 | while True: 42 | user_input = input("You: ") 43 | if user_input.lower() in ['exit', 'quit', 'bye']: 44 | break 45 | if user_input.lower() in ['/delete', '/clear']: 46 | await self.thread_manager.delete_thread(self.thread_id) 47 | self.thread_id = await self.get_or_create_thread() 48 | continue 49 | 50 | response = await self.get_latest_response(user_input) 51 | 52 | if response: 53 | print("Assistant:", response) 54 | 55 | finally: 56 | print(f"Session ended") 57 | 58 | async def get_or_create_thread(self): 59 | data = self.thread_manager.read_thread_data() 60 | thread_id = data.get('thread_id') 61 | if not thread_id: 62 | thread = await self.thread_manager.create_thread(messages=[]) 63 | thread_id = thread.id 64 | self.thread_manager.save_thread_data(thread_id) 65 | return thread_id 66 | 67 | async def find_or_create_assistant(self, name: str, model: str): 68 | """ 69 | Finds an existing assistant by name or creates a new one. 70 | 71 | Args: 72 | name (str): The name of the assistant. 73 | model (str): The model ID for the assistant. 74 | 75 | Returns: 76 | str: The ID of the found or created assistant. 77 | """ 78 | assistant_id = await self.assistant_manager.get_assistant_id_by_name(name) 79 | if not assistant_id: 80 | assistant = await self.assistant_manager.create_assistant(name=name, 81 | model=model, 82 | instructions="", 83 | tools=[{"type": "retrieval"}] 84 | ) 85 | assistant_id = assistant.id 86 | return assistant_id 87 | 88 | async def send_message(self, content): 89 | return await self.thread_manager.send_message(self.thread_id, content) 90 | 91 | async def display_chat_history(self): 92 | messages = await self.thread_manager.list_messages(self.thread_id) 93 | if messages is None: 94 | return 95 | print(messages) 96 | for message in reversed(messages.data): 97 | role = message.role 98 | content = message.content[0].text.value # Assuming message content is structured this way 99 | print(f"{role.title()}: {content}") 100 | 101 | async def get_latest_response(self, user_input): 102 | # Send the user message 103 | await self.send_message(user_input) 104 | 105 | # Create a new run for the assistant to respond 106 | await self.create_run() 107 | 108 | # Wait for the assistant's response 109 | await self.wait_for_assistant() 110 | 111 | # Retrieve the latest response 112 | return await self.retrieve_latest_response() 113 | 114 | async def create_run(self): 115 | return await self.thread_manager.create_run(self.thread_id, self.assistant_id) 116 | 117 | async def wait_for_assistant(self): 118 | while True: 119 | runs = await self.thread_manager.list_runs(self.thread_id) 120 | latest_run = runs.data[0] 121 | if latest_run.status in ["completed", "failed"]: 122 | break 123 | await asyncio.sleep(2) # Wait for 2 seconds before checking again 124 | 125 | async def retrieve_latest_response(self): 126 | response = await self.thread_manager.list_messages(self.thread_id) 127 | for message in response.data: 128 | if message.role == "assistant": 129 | return message.content[0].text.value 130 | return None 131 | --------------------------------------------------------------------------------