diff --git a/README.md b/README.md index 6004dfb..7747e4e 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,44 @@ This approach ensures more thoughtful, contextually aware, and reliable response - 🧠 **Reasoning Visibility**: Toggle visibility of the AI's thinking - 🔄 **Context Awareness**: Maintains conversation context for more coherent interactions +## 🚀 Versions +You can run each script variant: + +### Standard Version (rat.py) +The default implementation using DeepSeek for reasoning and OpenRouter for responses. +Run it using: +```bash +uv run rat.py +``` + +### Claude-Specific Version (rat-claude.py) +A specialized implementation designed for Claude models that leverages Anthropic's message prefilling capabilities. This version makes Claude believe the reasoning process is its own internal thought process, leading to more coherent and contextually aware responses. +Run it using: +```bash +uv run rat-claude.py +``` + +### Akash Version (rat-akash.py) +An implementation that uses the Akash network's API for both reasoning (DeepSeek-R1) and response generation (Llama models). This version provides access to powerful open-source models hosted on the decentralized Akash network. +Run it using: +```bash +uv run rat-akash.py +``` + ## ⚙️ Requirements • Python 3.11 or higher -• A .env file containing: +• A .env file containing one or more of: ```plaintext + # For standard version: DEEPSEEK_API_KEY=your_deepseek_api_key_here OPENROUTER_API_KEY=your_openrouter_api_key_here - optional + + # For Claude version: ANTHROPIC_API_KEY=your_anthropic_api_key_here + + # For Akash version: + AKASH_API_KEY=your_akash_api_key_here ``` ## 🚀 Installation @@ -60,7 +89,12 @@ This will install RAT as a command-line tool, allowing you to run it from anywhe OPENROUTER_API_KEY=your_openrouter_api_key_here optional ANTHROPIC_API_KEY=your_anthropic_api_key_here + AKASH_API_KEY=your_akash_api_key_here ``` + + To get an Akash API key: + - Visit https://chatapi.akash.network/ + - Click "Get Started" to generate a free API key 2. Run RAT from anywhere: ```bash @@ -75,24 +109,6 @@ This will install RAT as a command-line tool, allowing you to run it from anywhe -## 🚀 Versions -You can also run each script on its own: - -### Standard Version (rat.py) -The default implementation using DeepSeek for reasoning and OpenRouter for responses. -Run it using: -```bash -uv run rat.py -``` - -### Claude-Specific Version (rat-claude.py) -A specialized implementation designed for Claude models that leverages Anthropic's message prefilling capabilities. This version makes Claude believe the reasoning process is its own internal thought process, leading to more coherent and contextually aware responses. -Run it using: -```bash -uv run rat-claude.py -``` - - ## 🤝 Contributing Interested in improving RAT? diff --git a/rat-akash.py b/rat-akash.py new file mode 100644 index 0000000..84c4eef --- /dev/null +++ b/rat-akash.py @@ -0,0 +1,292 @@ +from openai import OpenAI +import os +from dotenv import load_dotenv +from rich import print as rprint +from rich.panel import Panel +from prompt_toolkit import PromptSession +from prompt_toolkit.styles import Style +import time +import argparse +import json + +# Model Constants +DEEPSEEK_MODEL = "DeepSeek-R1" +LLAMA_MODEL = "Meta-Llama-3-3-70B-Instruct" + +# Load environment variables +load_dotenv() + +class AkashModelChain: + """ + A class that implements the RAT (Retrieval Augmented Thinking) interface on the Akash network. + + This class handles the communication with DeepSeek and Llama models, managing message history, + and coordinating the reasoning and response generation process. + + Attributes: + stream (bool): Whether to stream responses from the models + client (OpenAI): The Akash API client + deepseek_messages (list): History of messages for the DeepSeek model + llama_messages (list): History of messages for the Llama model + current_model (str): The currently selected Llama model + show_reasoning (bool): Whether to display the reasoning process + """ + + def __init__(self, stream=True): + """ + Initialize the ModelChain with specified streaming preference. + + Args: + stream (bool, optional): Whether to stream model responses. Defaults to True. + """ + self.stream = stream + + # Initialize Akash client + self.client = OpenAI( + api_key=os.getenv("AKASH_API_KEY"), + base_url="https://chatapi.akash.network/api/v1" + ) + + self.deepseek_messages = [] + self.llama_messages = [] + self.current_model = LLAMA_MODEL + self.show_reasoning = True + + def set_model(self, model_name): + """ + Set the current model to use for responses. + + Args: + model_name (str): The name/ID of the model to use + """ + self.current_model = model_name + + def get_model_display_name(self): + """ + Get the name of the currently selected model. + + Returns: + str: The current model's name/ID + """ + return self.current_model + + def get_all_models(self): + """ + Retrieve a list of available models from the Akash client. + + Excludes DeepSeek-R1 models as they are primarily reasoning models + and not designed for generating direct responses. R1 models are + optimized for thinking/reasoning processes and are filtered out + to ensure only response-generation models are returned. + + Returns: + List[str]: A list of model IDs available for response generation + """ + models = self.client.models.list() + return [model.id for model in models.data if not 'DeepSeek-R1' in model.id] + + def get_deepseek_reasoning(self, user_input): + """ + Generate reasoning about the user's input using the DeepSeek model. + + This method sends the user's input to the DeepSeek model to generate + a thought process or reasoning about how to respond to the query. + + Args: + user_input (str): The user's input text + + Returns: + str: The generated reasoning content, cleaned of and tags + """ + start_time = time.time() + self.deepseek_messages.append({"role": "user", "content": user_input}) + + if self.show_reasoning: + rprint("\n[blue]Reasoning Process[/]") + + response = self.client.chat.completions.create( + model=DEEPSEEK_MODEL, + messages=self.deepseek_messages, + stream=self.stream + ) + + reasoning_content = "" + + if self.stream: + for chunk in response: + if chunk.choices[0].delta.content: + content_piece = chunk.choices[0].delta.content + reasoning_content += content_piece + if self.show_reasoning: + print(content_piece, end="", flush=True) + if "" in content_piece: + break + else: + content = response.choices[0].message.content + if self.show_reasoning: + print(content) + reasoning_content = content + if "" in content: + reasoning_content = content.split("")[0] + "" + + elapsed_time = time.time() - start_time + if elapsed_time >= 60: + time_str = f"{elapsed_time/60:.1f} minutes" + else: + time_str = f"{elapsed_time:.1f} seconds" + rprint(f"\n\n[yellow]Thought for {time_str}[/]") + + if self.show_reasoning: + print("\n") + + reasoning_content = reasoning_content.replace("", "").replace("", "") + return reasoning_content + + def get_llama_response(self, user_input, reasoning): + """ + Generate a response using the selected model. + + This method takes both the user's input and the generated reasoning + to produce a final response using the currently selected model. + + Args: + user_input (str): The user's input text + reasoning (str): The reasoning generated by DeepSeek + + Returns: + str: The generated response from the model + """ + # Create messages with reasoning included + messages = [ + {"role": "user", "content": user_input}, + {"role": "assistant", "content": f"{reasoning}"} + ] + + rprint(f"[green]{self.get_model_display_name()}[/]", end="") + + try: + if self.stream: + full_response = "" + response = self.client.chat.completions.create( + model=self.current_model, + messages=messages, + stream=self.stream + ) + for chunk in response: + if chunk.choices[0].delta.content: + text = chunk.choices[0].delta.content + print(text, end="", flush=True) + full_response += text + else: + response = self.client.chat.completions.create( + model=self.current_model, + messages=messages, + stream=self.stream + ) + full_response = response.choices[0].message.content + print(full_response) + + self.llama_messages.extend([ + {"role": "user", "content": user_input}, + {"role": "assistant", "content": full_response} + ]) + self.deepseek_messages.append({"role": "assistant", "content": full_response}) + + print("\n") + return full_response + + except Exception as e: + rprint(f"\n[red]Error in response: {str(e)}[/]") + return "Error occurred while getting response" + + def _save_message_history(self): + """Save both message histories to JSON files""" + try: + with open('deepseek_messages.json', 'w', encoding='utf-8') as f: + json.dump(self.deepseek_messages, f, indent=2, ensure_ascii=False) + + with open('llama_messages.json', 'w', encoding='utf-8') as f: + json.dump(self.llama_messages, f, indent=2, ensure_ascii=False) + except Exception as e: + rprint(f"\n[red]Error saving message history: {str(e)}[/]") + +def main(): + """ + Main function that runs the RAT Chat interface for the Akash network. + + This function sets up the command-line interface, processes user commands, + and manages the interaction between the user and the model chain. + """ + parser = argparse.ArgumentParser(description='RAT Chat Interface (Akash)') + parser.add_argument('--no-stream', action='store_true', + help='Disable streaming responses') + args = parser.parse_args() + + chain = AkashModelChain(stream=not args.no_stream) + + style = Style.from_dict({ + 'prompt': 'orange bold', + }) + session = PromptSession(style=style) + + rprint(Panel.fit( + "[bold cyan]Retrieval Augmented Thinking - Akash Edition[/]", + title="[bold cyan]RAT 🧠[/]", + border_style="cyan" + )) + + # Add streaming info to startup message + stream_info = "disabled" if args.no_stream else "enabled" + rprint(f"[yellow]Streaming: [bold]{stream_info}[/][/]") + + rprint("[yellow]Commands:[/]") + rprint(" • Type [bold red]'quit'[/] to exit") + rprint(" • Type [bold magenta]'model '[/] to change the Llama model") + rprint(" • Type [bold magenta]'reasoning'[/] to toggle reasoning visibility") + rprint(" • Type [bold magenta]'view_models'[/] to see all available models") + rprint(" • Type [bold magenta]'clear'[/] to clear chat history\n") + + while True: + try: + user_input = session.prompt("\nYou: ", style=style).strip() + + if user_input.lower() == 'quit': + print("\nGoodbye! 👋") + break + + if user_input.lower() == 'view_models': + available_models = chain.get_all_models() + rprint(f"\n[yellow]Available models:[/]") + for model in available_models: + rprint(f" • {model}") + print() + continue + + if user_input.lower() == 'clear': + chain.deepseek_messages = [] + chain.llama_messages = [] + rprint("\n[magenta]Chat history cleared![/]\n") + continue + + if user_input.lower().startswith('model '): + new_model = user_input[6:].strip() + chain.set_model(new_model) + print(f"\nChanged model to: {chain.get_model_display_name()}\n") + continue + + if user_input.lower() == 'reasoning': + chain.show_reasoning = not chain.show_reasoning + status = "visible" if chain.show_reasoning else "hidden" + rprint(f"\n[magenta]Reasoning process is now {status}[/]\n") + continue + + reasoning = chain.get_deepseek_reasoning(user_input) + llama_response = chain.get_llama_response(user_input, reasoning) + + except KeyboardInterrupt: + continue + except EOFError: + break + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rat-claude.py b/rat-claude.py index d61780d..52e4554 100644 --- a/rat-claude.py +++ b/rat-claude.py @@ -7,21 +7,35 @@ from prompt_toolkit import PromptSession from prompt_toolkit.styles import Style import time +import argparse +import json # Model Constants DEEPSEEK_MODEL = "deepseek-reasoner" -CLAUDE_MODEL = "claude-3-5-sonnet-20241022" +DEEPSEEK_MODEL_AKASH = "DeepSeek-R1" +CLAUDE_MODEL = "claude-3-5-haiku-20241022" # Load environment variables load_dotenv() class ModelChain: - def __init__(self): - # Initialize DeepSeek client - self.deepseek_client = OpenAI( - api_key=os.getenv("DEEPSEEK_API_KEY"), - base_url="https://api.deepseek.com" - ) + def __init__(self, use_deepseek_openrouter=False, stream=True): + self.use_deepseek_openrouter = use_deepseek_openrouter + self.stream = stream + + # Initialize appropriate DeepSeek client based on use_deepseek flag + if self.use_deepseek_openrouter: + self.deepseek_client = OpenAI( + api_key=os.getenv("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com" + ) + self.deepseek_model = DEEPSEEK_MODEL + else: + self.deepseek_client = OpenAI( + api_key=os.getenv("AKASH_API_KEY"), + base_url="https://chatapi.akash.network/api/v1" + ) + self.deepseek_model = DEEPSEEK_MODEL_AKASH # Initialize Claude client self.claude_client = anthropic.Anthropic( @@ -46,24 +60,53 @@ def get_deepseek_reasoning(self, user_input): if self.show_reasoning: rprint("\n[blue]Reasoning Process[/]") - response = self.deepseek_client.chat.completions.create( - model=DEEPSEEK_MODEL, - max_tokens=1, - messages=self.deepseek_messages, - stream=True - ) + # Create completion parameters + completion_params = { + "model": self.deepseek_model, + "messages": self.deepseek_messages, + "stream": self.stream + } + + # Only add max_tokens if using OpenRouter (not Akash) + if self.use_deepseek_openrouter: + completion_params["max_tokens"] = 1 + + response = self.deepseek_client.chat.completions.create(**completion_params) reasoning_content = "" final_content = "" - for chunk in response: - if chunk.choices[0].delta.reasoning_content: - reasoning_piece = chunk.choices[0].delta.reasoning_content - reasoning_content += reasoning_piece - if self.show_reasoning: - print(reasoning_piece, end="", flush=True) - elif chunk.choices[0].delta.content: - final_content += chunk.choices[0].delta.content + if self.stream: + for chunk in response: + if self.use_deepseek_openrouter and chunk.choices[0].delta.reasoning_content: + # Original DeepSeek API handling + reasoning_piece = chunk.choices[0].delta.reasoning_content + reasoning_content += reasoning_piece + if self.show_reasoning: + print(reasoning_piece, end="", flush=True) + if "" in reasoning_piece: + break + elif chunk.choices[0].delta.content: + # Akash API or regular content handling + content_piece = chunk.choices[0].delta.content + if not self.use_deepseek_openrouter: + reasoning_content += content_piece + if self.show_reasoning: + print(content_piece, end="", flush=True) + if "" in content_piece: + break + else: + final_content += content_piece + if "" in final_content: + break + else: + # Non-streaming response handling + content = response.choices[0].message.content + if self.show_reasoning: + print(content) + reasoning_content = content + if "" in content: + reasoning_content = content.split("")[0] + "" elapsed_time = time.time() - start_time if elapsed_time >= 60: @@ -74,6 +117,8 @@ def get_deepseek_reasoning(self, user_input): if self.show_reasoning: print("\n") + + reasoning_content = reasoning_content.replace("", "").replace("", "") return reasoning_content def get_claude_response(self, user_input, reasoning): @@ -103,15 +148,24 @@ def get_claude_response(self, user_input, reasoning): rprint(f"[green]{self.get_model_display_name()}[/]", end="") try: - with self.claude_client.messages.stream( - model=self.current_model, - messages=messages, - max_tokens=8000 - ) as stream: - full_response = "" - for text in stream.text_stream: - print(text, end="", flush=True) - full_response += text + if self.stream: + with self.claude_client.messages.stream( + model=self.current_model, + messages=messages, + max_tokens=8000 + ) as stream: + full_response = "" + for text in stream.text_stream: + print(text, end="", flush=True) + full_response += text + else: + response = self.claude_client.messages.create( + model=self.current_model, + messages=messages, + max_tokens=8000 + ) + full_response = response.content[0].text + print(full_response) self.claude_messages.extend([ user_message, @@ -123,14 +177,40 @@ def get_claude_response(self, user_input, reasoning): self.deepseek_messages.append({"role": "assistant", "content": full_response}) print("\n") + + # Save message histories to JSON files after each response + # self._save_message_history() + return full_response except Exception as e: rprint(f"\n[red]Error in response: {str(e)}[/]") return "Error occurred while getting response" + + def _save_message_history(self): + """Save both message histories to JSON files""" + try: + with open('deepseek_messages.json', 'w', encoding='utf-8') as f: + json.dump(self.deepseek_messages, f, indent=2, ensure_ascii=False) + + with open('claude_messages.json', 'w', encoding='utf-8') as f: + json.dump(self.claude_messages, f, indent=2, ensure_ascii=False) + except Exception as e: + rprint(f"\n[red]Error saving message history: {str(e)}[/]") def main(): - chain = ModelChain() + # Add argument parsing for API choice + parser = argparse.ArgumentParser(description='RAT Chat Interface') + parser.add_argument('--use-deepseek', action='store_true', + help='Use original DeepSeek API instead of Akash Chat API') + parser.add_argument('--no-stream', action='store_true', + help='Disable streaming responses') + args = parser.parse_args() + + chain = ModelChain( + use_deepseek_openrouter=args.use_deepseek, + stream=not args.no_stream + ) style = Style.from_dict({ 'prompt': 'orange bold', @@ -142,6 +222,15 @@ def main(): title="[bold cyan]RAT 🧠[/]", border_style="cyan" )) + + # Add API info to startup message + api_info = "Original DeepSeek API" if args.use_deepseek else "Akash Chat API" + rprint(f"[yellow]Using: [bold]{api_info}[/][/]") + + # Add streaming info to startup message + stream_info = "disabled" if args.no_stream else "enabled" + rprint(f"[yellow]Streaming: [bold]{stream_info}[/][/]") + rprint("[yellow]Commands:[/]") rprint(" • Type [bold red]'quit'[/] to exit") rprint(" • Type [bold magenta]'model '[/] to change the Claude model")