Skip to content

Commit 697a42d

Browse files
authored
Merge pull request #18 from jasonacox/v0.15.18
Chatbot update - model and image handling
2 parents fab4484 + 5980ef4 commit 697a42d

File tree

4 files changed

+83
-30
lines changed

4 files changed

+83
-30
lines changed

Diff for: RELEASE.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Releases
22

3+
## 0.15.18 - Chatbot Updates
4+
5+
* Model selection will now be stored as a cookie to allow it to persist between sessions.
6+
* Image handling has been updated to recover when switching between vision models and language models. A new `MAX_IMAGES` setting has been added to allow persisting more than one image in the same conversation context (must be supported by model or the images will be pruned by chatbot)
7+
* Model selection option `/model list` will display list of available models in chat windows.
8+
39
## 0.15.17 - Model Selector
410

511
* Chatbot - The `/model` command will now initiate a UI popup window and dropdown to allow the use to select a model from the list of available models. Alternatively, the user can specify the model with the command (e.g. `/model mixtral`) to select it immediately without the popup.

Diff for: chatbot/server.py

+64-22
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
* EXTRA_BODY - Extra body parameters for OpenAI API
4747
* TOXIC_THRESHOLD - Toxicity threshold for responses 0-1 or 99 disable
4848
* THINKING - Set to True to enable thinking mode by default
49+
* MAX_IMAGES - Maximum number of images the chatbot will keep in context (default 1)
4950
5051
Running a llama-cpp-python server:
5152
* CMAKE_ARGS="-DLLAMA_CUBLAS=on" FORCE_CMAKE=1 pip install llama-cpp-python
@@ -141,6 +142,7 @@ def debug(text):
141142
TOXIC_THRESHOLD = float(os.environ.get("TOXIC_THRESHOLD", 99)) # Toxicity threshold for responses 0-1 or 99 disable
142143
THINKING = os.environ.get("THINKING", "false").lower() == "true" # Set to True to enable thinking mode by default
143144
THINK_FILTER = os.environ.get("THINK_FILTER", "false").lower() == "true" # Set to True to enable thinking filter
145+
MAX_IMAGES = int(os.environ.get("MAX_IMAGES", 1)) # Maximum number of images to keep in context
144146

145147
# Convert EXTRA_BODY to dictionary if it is proper JSON
146148
if EXTRA_BODY:
@@ -457,20 +459,24 @@ async def ask(prompt, sid=None):
457459
client[sid]["context"] = base_prompt()
458460
# Process image upload if present
459461
if client[sid]["image_data"]:
460-
# Remove previous image data from context
461-
for turn in client[sid]["context"]:
462-
# if turn["content"] is a list, remove image_url
462+
# go through context and count images, remove if too many
463+
image_count = 0
464+
for turn in reversed(client[sid]["context"]):
463465
if "content" in turn and isinstance(turn["content"], list):
464-
# convert list to string of text
465-
turn["content"] = ' '.join([x.get("text", "") for x in turn["content"]])
466+
for item in turn["content"]:
467+
if "image_url" in item:
468+
image_count += 1
469+
if image_count >= MAX_IMAGES:
470+
# remove image from context
471+
debug("Too many images - Found image in context, removing 1...")
472+
turn["content"] = ' '.join([x.get("text", "") for x in turn["content"]])
466473
message = {
467474
"role": "user",
468475
"content": [
469476
{"type": "text", "text": prompt},
470477
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{client[sid]['image_data']}"}}
471478
]
472479
}
473-
client[sid]["image_data"] = ""
474480
client[sid]["context"].append(message)
475481
else:
476482
client[sid]["context"].append({"role": "user", "content": prompt})
@@ -484,6 +490,7 @@ async def ask(prompt, sid=None):
484490
messages=client[sid]["context"],
485491
extra_body=EXTRA_BODY,
486492
)
493+
client[sid]["image_data"] = ""
487494
except openai.OpenAIError as erro:
488495
# If we get an error, try to recover
489496
client[sid]["context"].pop()
@@ -494,8 +501,8 @@ async def ask(prompt, sid=None):
494501
# set client model to default
495502
client[sid]["model"] = MYMODEL
496503
# update footer
497-
await sio.emit('update', {'update': f"TinyLLM Chatbot {VERSION} - {client[sid]['model']} ",
498-
'voice': 'footer'},room=sid)
504+
await sio.emit('update', {'update': f"TinyLLM Chatbot {VERSION} - {client[sid]['model']} ",
505+
'voice': 'footer', 'model': client[sid]['model']},room=sid)
499506
elif "maximum context length" in str(erro):
500507
if len(prompt) > 1000:
501508
# assume we have very large prompt - cut out the middle
@@ -509,11 +516,40 @@ async def ask(prompt, sid=None):
509516
# our context has grown too large, reset
510517
client[sid]["context"] = base_prompt()
511518
log(f"Session {sid} - Reset context to base prompt - Now: ~{len(client[sid]['context'])/4} tokens")
519+
elif "At most" in str(erro) and "image" in str(erro):
520+
# Remove oldest image from context
521+
for turn in reversed(client[sid]["context"]):
522+
# if turn["content"] is a list, remove image_url
523+
if "content" in turn and isinstance(turn["content"], list):
524+
debug("Too many images - Found last image in context, removing...")
525+
turn["content"] = ' '.join([x.get("text", "") for x in turn["content"]])
526+
break
527+
continue
528+
elif "Internal Server Error" in str(erro):
529+
# Check to see if our context has images - if so, remove them
530+
debug("Internal Server Error - Checking for images in context...")
531+
removed_image_data = False
532+
for turn in client[sid]["context"]:
533+
# if turn["content"] is a list, remove image_url
534+
if "content" in turn and isinstance(turn["content"], list):
535+
log("Found image in context, removing...")
536+
removed_image_data = True
537+
turn["content"] = ' '.join([x.get("text", "") for x in turn["content"]])
538+
if removed_image_data:
539+
# remove last turn in context and retry
540+
await sio.emit('update', {'update': '[Images do not seem to be supported by model... Removing]', 'voice': 'user'},room=sid)
541+
client[sid]["context"].pop()
542+
continue
543+
log(f"ERROR: {str(erro)}")
544+
stats["errors"] += 1
545+
await sio.emit('update', {'update': str(erro), 'voice': 'user'},room=sid)
546+
break
512547
else:
548+
# If all else fails, log the error and break
513549
log(f"ERROR: {str(erro)}")
514550
stats["errors"] += 1
515551
await sio.emit('update', {'update': str(erro), 'voice': 'user'},room=sid)
516-
552+
break
517553
if not client[sid]["remember"]:
518554
client[sid]["remember"] =True
519555
client[sid]["context"].pop()
@@ -915,7 +951,7 @@ async def send_update(session_id):
915951
await sio.sleep(0.1)
916952
else:
917953
# Check to see of CoT is enabled but not while processing a file/image
918-
client_cot = client[session_id]["cot"]
954+
client_cot = client[session_id]["cot"]
919955
client_image_data = client[session_id]["image_data"]
920956
client_visible = client[session_id]["visible"]
921957
if client_cot and not client_image_data and client_visible:
@@ -978,7 +1014,7 @@ async def send_update(session_id):
9781014
# Update footer with stats
9791015
await sio.emit('update', {'update':
9801016
f"TinyLLM Chatbot {VERSION} - {client[session_id]['model']} - Tokens: {tokens} - TPS: {tokens/(time.time()-stime):.1f}",
981-
'voice': 'footer'},room=session_id)
1017+
'voice': 'footer', 'model': client[session_id]['model']},room=session_id)
9821018
# Check for link injection
9831019
if client[session_id]["links"]:
9841020
await sio.emit('update', {'update': json.dumps(client[session_id]["links"]), 'voice': 'links'},room=session_id)
@@ -1052,7 +1088,7 @@ async def handle_disconnect(session_id):
10521088
# shutdown thread
10531089
client[session_id]["stop_thread_flag"] = True
10541090
client.pop(session_id)
1055-
1091+
10561092
# Change the LLM model
10571093
@sio.on('model')
10581094
async def change_model(session_id, model):
@@ -1062,13 +1098,18 @@ async def change_model(session_id, model):
10621098
list_of_models = get_models()
10631099
if model not in list_of_models:
10641100
log(f"Requested invalid model {model}")
1065-
await sio.emit('update', {'update': f"Model not found: {model}", 'voice': 'user'}, room=session_id)
1101+
if len(client[session_id]["context"]) > 2:
1102+
await sio.emit('update', {'update': f"Model not found: {model}", 'voice': 'user'}, room=session_id)
10661103
return
10671104
debug(f"Changing model for {session_id} to {model}")
10681105
client[session_id]["model"] = model
10691106
# Update footer
1070-
await sio.emit('update', {'update': f"TinyLLM Chatbot {VERSION} - {model}", 'voice': 'footer'}, room=session_id)
1071-
await sio.emit('update', {'update': f'[Model changed to {model}]', 'voice': 'user'}, room=session_id)
1107+
await sio.emit('update', {'update': f"TinyLLM Chatbot {VERSION} - {model}", 'voice': 'footer',
1108+
'model': model}, room=session_id)
1109+
# Check to see if this is a new session
1110+
log(f"context length: {len(client[session_id]['context'])}")
1111+
if len(client[session_id]["context"]) > 2:
1112+
await sio.emit('update', {'update': f'[Model changed to {model}]', 'voice': 'user'}, room=session_id)
10721113
else:
10731114
log(f"Invalid session {session_id}")
10741115
await handle_invalid_session(session_id)
@@ -1332,19 +1373,20 @@ async def handle_model_command(session_id, p):
13321373
if not args:
13331374
# Open Model Dialog
13341375
await sio.emit('model_dialog', {}, room=session_id)
1335-
#model_list = get_models()
1336-
#msg = f'Current LLM Model: {client[session_id]["model"]}\n'
1337-
#msg += f'- Available Models: {", ".join(model_list)}\n'
1338-
#msg += '- Usage: /model {model_name}'
1339-
#await sio.emit('update', {'update': msg, 'voice': 'user'}, room=session_id)
13401376
return
13411377
model_list = get_models()
13421378
if not args in model_list:
13431379
args = args.lower()
13441380
if args in model_list:
13451381
client[session_id]["model"] = args
1346-
await sio.emit('update', {'update': f'[LLM Model set to {args}]', 'voice': 'user'}, room=session_id)
1347-
await sio.emit('update', {'update': f"TinyLLM Chatbot {args} - {client[session_id]['model']}", 'voice': 'footer'}, room=session_id)
1382+
await sio.emit('update', {'update': f'[Model changed to {args}]', 'voice': 'user'}, room=session_id)
1383+
await sio.emit('update', {'update': f"TinyLLM Chatbot {VERSION} - {args}", 'voice': 'footer',
1384+
'model': args}, room=session_id)
1385+
elif args in ["list", "help"]:
1386+
msg = f'Current LLM Model: {client[session_id]["model"]}\n'
1387+
msg += f'- Available Models: {", ".join(model_list)}\n'
1388+
msg += '- Usage: /model {model_name}'
1389+
await sio.emit('update', {'update': msg, 'voice': 'user'}, room=session_id)
13481390
else:
13491391
await sio.emit('update', {'update': f'[Model {args} not found]', 'voice': 'user'}, room=session_id)
13501392

Diff for: chatbot/templates/index.html

+12-7
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ <h2 class="popup-title">LLM Models</h2>
725725
// Clear form
726726
document.getElementById('messageInput').value = "";
727727

728-
// Send a POST request to the Flask route
728+
// Send a POST request to the backend
729729
fetch('/send_message', {
730730
method: 'POST',
731731
headers: {
@@ -735,8 +735,8 @@ <h2 class="popup-title">LLM Models</h2>
735735
})
736736
.then(response => response.json())
737737
.then(data => {
738-
console.log('Response from Flask:', data);
739-
// Handle the response from Flask as needed
738+
console.log('Response from backend:', data);
739+
// Handle the response from backend as needed
740740
})
741741
.catch(error => {
742742
console.error('Error:', error);
@@ -805,14 +805,13 @@ <h2 class="popup-title">LLM Models</h2>
805805
messageContainer.innerHTML = messageText;
806806
}
807807

808-
if (data.voice === "model") {
809-
getVersion();
810-
}
811-
812808
if (data.voice === "footer") {
813809
// Update the footer with the update message
814810
document.getElementById('footer').innerHTML = data.update +
815811
' - <a href="javascript:void(0);" onclick="requestConversation()">Debug Session</a>'
812+
// Set my_model cookie if model is in the update message
813+
document.cookie = "my_model=" + data.model;
814+
console.log('Model cookie set:', data.model);
816815
}
817816

818817
if (data.voice === "conversation") {
@@ -956,6 +955,12 @@ <h2 class="popup-title">LLM Models</h2>
956955

957956
// Fetch version and update footer
958957
getVersion();
958+
959+
// Check for my_model cookie and request the model
960+
var model = document.cookie.replace(/(?:(?:^|.*;\s*)my_model\s*\=\s*([^;]*).*$)|^.*$/, "$1");
961+
if (model) {
962+
socket.emit('model', model);
963+
}
959964
};
960965

961966
// On window resize, scroll to the bottom of the message container

Diff for: chatbot/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "v0.15.17"
1+
VERSION = "v0.15.18"

0 commit comments

Comments
 (0)