mirror of https://github.com/redis/redis.git
CLI tool added as example / utility.
This commit is contained in:
parent
182737f3cc
commit
6d1a15987b
|
|
@ -0,0 +1 @@
|
||||||
|
venv
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
This tool is similar to redis-cli (but very basic) but allows
|
||||||
|
to specify arguments that are expanded as fectors by calling
|
||||||
|
ollama to get the embedding.
|
||||||
|
|
||||||
|
Whatever is passed as !"foo bar" gets expanded into
|
||||||
|
VALUES ... embedding ...
|
||||||
|
|
||||||
|
You must have ollama running with the mxbai-emb-large model
|
||||||
|
already installed for this to work.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
redis> KEYS *
|
||||||
|
1) food_items
|
||||||
|
2) glove_embeddings_bin
|
||||||
|
3) many_movies_mxbai-embed-large_BIN
|
||||||
|
4) many_movies_mxbai-embed-large_NOQUANT
|
||||||
|
5) word_embeddings
|
||||||
|
6) word_embeddings_bin
|
||||||
|
7) glove_embeddings_fp32
|
||||||
|
|
||||||
|
redis> VSIM food_items !"drinks with fruit"
|
||||||
|
1) (Fruit)Juices,Lemonade,100ml,50 cal,210 kJ
|
||||||
|
2) (Fruit)Juices,Limeade,100ml,128 cal,538 kJ
|
||||||
|
3) CannedFruit,Canned Fruit Cocktail,100g,81 cal,340 kJ
|
||||||
|
4) (Fruit)Juices,Energy-Drink,100ml,87 cal,365 kJ
|
||||||
|
5) Fruits,Lime,100g,30 cal,126 kJ
|
||||||
|
6) (Fruit)Juices,Coconut Water,100ml,19 cal,80 kJ
|
||||||
|
7) Fruits,Lemon,100g,29 cal,122 kJ
|
||||||
|
8) (Fruit)Juices,Clamato,100ml,60 cal,252 kJ
|
||||||
|
9) Fruits,Fruit salad,100g,50 cal,210 kJ
|
||||||
|
10) (Fruit)Juices,Capri-Sun,100ml,41 cal,172 kJ
|
||||||
|
|
||||||
|
redis> vsim food_items !"barilla"
|
||||||
|
1) Pasta&Noodles,Spirelli,100g,367 cal,1541 kJ
|
||||||
|
2) Pasta&Noodles,Farfalle,100g,358 cal,1504 kJ
|
||||||
|
3) Pasta&Noodles,Capellini,100g,353 cal,1483 kJ
|
||||||
|
4) Pasta&Noodles,Spaetzle,100g,368 cal,1546 kJ
|
||||||
|
5) Pasta&Noodles,Cappelletti,100g,164 cal,689 kJ
|
||||||
|
6) Pasta&Noodles,Penne,100g,351 cal,1474 kJ
|
||||||
|
7) Pasta&Noodles,Shells,100g,353 cal,1483 kJ
|
||||||
|
8) Pasta&Noodles,Linguine,100g,357 cal,1499 kJ
|
||||||
|
9) Pasta&Noodles,Rotini,100g,353 cal,1483 kJ
|
||||||
|
10) Pasta&Noodles,Rigatoni,100g,353 cal,1483 kJ
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import redis
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
from prompt_toolkit import PromptSession
|
||||||
|
from prompt_toolkit.history import InMemoryHistory
|
||||||
|
|
||||||
|
def get_embedding(text):
|
||||||
|
"""Get embedding from local Ollama API"""
|
||||||
|
url = "http://localhost:11434/api/embeddings"
|
||||||
|
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():
|
||||||
|
# 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()
|
||||||
Loading…
Reference in New Issue