# # Copyright (c) 2009-Present, Redis Ltd. # All rights reserved. # # Licensed under your choice of (a) the Redis Source Available License 2.0 # (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the # GNU Affero General Public License v3 (AGPLv3). # #!/usr/bin/env python3 import argparse import redis import requests import re import shlex from prompt_toolkit import PromptSession from prompt_toolkit.history import InMemoryHistory # Default Ollama embeddings URL (can be overridden with --ollama-url) OLLAMA_URL = "http://localhost:11434/api/embeddings" def get_embedding(text): """Get embedding from local Ollama API""" url = OLLAMA_URL payload = { "model": "mxbai-embed-large", "prompt": text } try: response = requests.post(url, json=payload) response.raise_for_status() return response.json()['embedding'] except requests.exceptions.RequestException as e: raise Exception(f"Failed to get embedding: {str(e)}") def process_embedding_patterns(text): """Process !"text" and !!"text" patterns in the command""" def replace_with_embedding(match): text = match.group(1) embedding = get_embedding(text) return f"VALUES {len(embedding)} {' '.join(map(str, embedding))}" def replace_with_embedding_and_text(match): text = match.group(1) embedding = get_embedding(text) # Return both the embedding values and the original text as next argument return f'VALUES {len(embedding)} {" ".join(map(str, embedding))} "{text}"' # First handle !!"text" pattern (must be done before !"text") text = re.sub(r'!!"([^"]*)"', replace_with_embedding_and_text, text) # Then handle !"text" pattern text = re.sub(r'!"([^"]*)"', replace_with_embedding, text) return text def parse_command(command): """Parse command respecting quoted strings""" try: # Use shlex to properly handle quoted strings return shlex.split(command) except ValueError as e: raise Exception(f"Invalid command syntax: {str(e)}") def format_response(response): """Format the response to match Redis protocol style""" if response is None: return "(nil)" elif isinstance(response, bool): return "+OK" if response else "(error) Operation failed" elif isinstance(response, (list, set)): if not response: return "(empty list or set)" return "\n".join(f"{i+1}) {item}" for i, item in enumerate(response)) elif isinstance(response, int): return f"(integer) {response}" else: return str(response) def main(): global OLLAMA_URL parser = argparse.ArgumentParser(prog="cli.py", add_help=False) parser.add_argument("--ollama-url", dest="ollama_url", help="Ollama embeddings API URL (default: {OLLAMA_URL})", default=OLLAMA_URL) args, _ = parser.parse_known_args() OLLAMA_URL = args.ollama_url # Default connection to localhost:6379 r = redis.Redis(host='localhost', port=6379, decode_responses=True) try: # Test connection r.ping() print("Connected to Redis. Type your commands (CTRL+D to exit):") print("Special syntax:") print(" !\"text\" - Replace with embedding") print(" !!\"text\" - Replace with embedding and append text as value") print(" \"text\" - Quote strings containing spaces") except redis.ConnectionError: print("Error: Could not connect to Redis server") return # Setup prompt session with history session = PromptSession(history=InMemoryHistory()) # Main loop while True: try: # Read input with line editing support command = session.prompt("redis> ") # Skip empty commands if not command.strip(): continue # Process any embedding patterns before parsing try: processed_command = process_embedding_patterns(command) except Exception as e: print(f"(error) Embedding processing failed: {str(e)}") continue # Parse the command respecting quoted strings try: parts = parse_command(processed_command) except Exception as e: print(f"(error) {str(e)}") continue if not parts: continue cmd = parts[0].lower() args = parts[1:] # Execute command try: method = getattr(r, cmd, None) if method is not None: result = method(*args) else: # Use execute_command for unknown commands result = r.execute_command(cmd, *args) print(format_response(result)) except AttributeError: print(f"(error) Unknown command '{cmd}'") except EOFError: print("\nGoodbye!") break except KeyboardInterrupt: continue # Allow Ctrl+C to clear current line except redis.RedisError as e: print(f"(error) {str(e)}") except Exception as e: print(f"(error) {str(e)}") if __name__ == "__main__": main()