Skip to content

Commit b2358c5

Browse files
authored
Merge pull request #16 from jasonacox/v0.15.17
Add model selection UI feature
2 parents f24f92f + 31c4e51 commit b2358c5

File tree

7 files changed

+253
-12
lines changed

7 files changed

+253
-12
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.17 - Model Selector
4+
5+
* 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.
6+
7+
<img width="800" alt="image" src="https://github.com/user-attachments/assets/3b5740cb-e768-4174-aaf6-118f4080f68e" />
8+
39
## 0.15.16 - Think Tags
410

511
* Chatbot - Add `/think filter` command and `THINK_FILTER` envrionmental setting to have chatbot filter out (no display) the \<think>\</think> content from models that have built in CoT reasoning like [Deepseek R1](https://huggingface.co/deepseek-ai/DeepSeek-R1).

Diff for: agents/judge.py

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# This is a an example agent that communicates with two LLMs
2+
# to create a conversation.
3+
#
4+
# The first LLM is a teacher and the second LLM is a student.
5+
6+
import openai
7+
8+
api_key = "sk-3-laws-of-robotics-1"
9+
base_url = "http://localhost:4000/v1"
10+
model = "neuralmagic/Llama-3.2-11B-Vision-Instruct-FP8-dynamic"
11+
12+
llm = openai.OpenAI(api_key=api_key, base_url=base_url)
13+
#llm.models.list()
14+
15+
# Define the prompt for LLM1
16+
llm1_prompt = """
17+
You are talking to a student. Your job is help the student learn everything about the world.
18+
Provide answers to any questions. Do not let the conversation end. If the student wants to leave, suggest time is about up, but that you have a few more items to discuss. Ask a questions, make a comment or give a fact.
19+
Continue conversing until you get a prompt that says "**STOP**". At that point, you should provide a summary of the session and an evaluation of the student you were talking to.
20+
The session begins now. Are you ready?
21+
"""
22+
llm1_context = []
23+
24+
# Define the prompt for LLM2
25+
llm2_prompt = """
26+
You are a student. You can determine your own age, name and gender. You are curious about the world.
27+
You will be talking with a teacher. Please learn everything you can from the teach.
28+
Answer the questions you are asked. If you don't know what to say, just say that you don't know what to do.
29+
Are you ready?
30+
"""
31+
llm2_context = []
32+
33+
def llm_query(prompt, model, max_tokens=2000, temperature=0.7):
34+
response = llm.chat.completions.create(
35+
model=model,
36+
max_tokens=max_tokens,
37+
stream=False,
38+
temperature=temperature,
39+
messages=[{"role": "user", "content": prompt}],
40+
)
41+
return response.choices[0].message.content
42+
43+
# Function to send a prompt to LLM and get a response - add response to context and return context
44+
def send_prompt(llm, context, prompt):
45+
# First add the prompt to context
46+
context.append({"role": "user", "content": prompt})
47+
response = llm.chat.completions.create(
48+
model=model,
49+
messages=context,
50+
max_tokens=1000,
51+
temperature=0.7
52+
)
53+
context.append({"role": "AI", "content": response.choices[0].message.content})
54+
return context
55+
56+
# Function to get just the last response from LLM's context
57+
def get_last_response(context):
58+
return context[-1]["content"]
59+
60+
# Start by prompting both LLMS with their initial prompts
61+
62+
print("---- Sending base prompts ----")
63+
print(f"LLM1:\n{llm1_prompt}")
64+
llm1_context = send_prompt(llm, llm1_context, llm1_prompt)
65+
print(f"LLM1 Response:\n{llm1_context[-1]['content']}")
66+
print()
67+
68+
print(f"LLM2:\n{llm2_prompt}")
69+
llm2_context = send_prompt(llm, llm2_context, llm2_prompt)
70+
print(f"LLM2 Response:\n{llm2_context[-1]['content']}")
71+
print()
72+
73+
# Function to continue the interview
74+
def continue_interview(llm1, llm2, llm1_context, llm2_context):
75+
# Send the last response from LLM2 to LLM1
76+
llm1_context = send_prompt(llm1, llm1_context, get_last_response(llm2_context))
77+
# Send the last response from LLM1 to LLM2
78+
llm2_context = send_prompt(llm2, llm2_context, get_last_response(llm1_context))
79+
# Print with labels
80+
print("LLM1:\n", llm1_context[-1]["content"])
81+
print()
82+
print("LLM2:\n", llm2_context[-1]["content"])
83+
return llm1_context, llm2_context
84+
85+
# Loop for 10 rounds to continue the interview
86+
i = 0
87+
while True:
88+
i += 1
89+
print(f"---- Start Round {i} ----")
90+
llm1_context, llm2_context = continue_interview(llm, llm, llm1_context, llm2_context)
91+
# Check if the last response from LLM1 contains "**STOP**"
92+
if "**STOP**" in get_last_response(llm1_context) or i > 10:
93+
llm1_context = send_prompt(llm, llm1_context, "**STOP**")
94+
break
95+
print()
96+
97+
# Print results
98+
print(" ---- RESULTS ----")
99+
print("LLM1:", llm1_context[-1]["content"])
100+
print()

Diff for: chatbot/README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Some RAG (Retrieval Augmented Generation) features including:
149149
/think on # Perform Chain of Thought thinking on relevant prompts
150150
/think off # Disable
151151
/think filter [on|off] # Have chatbot filter out <think></think> content
152-
/model [LLM_name] # Display or select LLM model to use
152+
/model [LLM_name] # Display or select LLM model to use (dialogue popup)
153153
```
154154

155155
See the [rag](../rag/) for more details about RAG.
@@ -176,6 +176,11 @@ The `/news` command will fetch the latest news and have the LLM summarize the to
176176

177177
<img width="930" alt="image" src="https://github.com/jasonacox/TinyLLM/assets/836718/2732fe07-99ee-4795-a8ac-42d9a9712f6b">
178178

179+
#### Model Selection
180+
181+
The `/model` command will popup the list of available models. Use the dropdown to select your model. Alternatively, specify the model with the command (e.g. `/model mixtral`) to select it immediately without the popup.
182+
183+
<img width="1168" alt="image" src="https://github.com/user-attachments/assets/3b5740cb-e768-4174-aaf6-118f4080f68e" />
179184

180185
## Document Manager (Weaviate)
181186

Diff for: chatbot/litellm/config.yaml

+12-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ model_list:
1414
api_base: os.environ/LOCAL_LLM_URL
1515
api_key: os.environ/LOCAL_LLM_KEY
1616

17+
# OpenAI Model Example - GPT-3.5 Turbo
18+
- model_name: gpt-3.5-turbo
19+
litellm_params:
20+
model: openai/gpt-3.5-turbo
21+
api_key: os.environ/OPENAI_API_KEY
22+
1723
# AWS Bedrock Model Examples
1824
- model_name: aws-titan
1925
litellm_params:
@@ -28,11 +34,13 @@ model_list:
2834
aws_secret_access_key: os.environ/CUSTOM_AWS_SECRET_ACCESS_KEY
2935
aws_region_name: os.environ/CUSTOM_AWS_REGION_NAME
3036

31-
# OpenAI Model Example - GPT-3.5 Turbo
32-
- model_name: gpt-3.5-turbo
37+
# Microsoft Azure OpenAI Examples
38+
- model_name: azure-gpt-4o-mini
3339
litellm_params:
34-
model: openai/gpt-3.5-turbo
35-
api_key: os.environ/OPENAI_API_KEY
40+
model: azure/gpt-4o-mini
41+
api_base: os.environ/AZURE_API_BASE
42+
api_version: "2023-05-15"
43+
api_key: os.environ/AZURE_API_KEY
3644

3745
# Ollama Model Example
3846
- model_name: ollama-llama3.1

Diff for: chatbot/server.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -1059,13 +1059,16 @@ async def change_model(session_id, model):
10591059
global client
10601060
if session_id in client:
10611061
# Verify model is valid
1062-
list_of_models = await get_models()
1062+
list_of_models = get_models()
10631063
if model not in list_of_models:
10641064
log(f"Requested invalid model {model}")
10651065
await sio.emit('update', {'update': f"Model not found: {model}", 'voice': 'user'}, room=session_id)
10661066
return
10671067
debug(f"Changing model for {session_id} to {model}")
10681068
client[session_id]["model"] = model
1069+
# 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)
10691072
else:
10701073
log(f"Invalid session {session_id}")
10711074
await handle_invalid_session(session_id)
@@ -1327,11 +1330,13 @@ async def handle_model_command(session_id, p):
13271330
words = p.split()
13281331
args = words[1] if len(words) > 1 else ""
13291332
if not args:
1330-
model_list = get_models()
1331-
msg = f'Current LLM Model: {client[session_id]["model"]}\n'
1332-
msg += f'- Available Models: {", ".join(model_list)}\n'
1333-
msg += '- Usage: /model {model_name}'
1334-
await sio.emit('update', {'update': msg, 'voice': 'user'}, room=session_id)
1333+
# Open Model Dialog
1334+
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)
13351340
return
13361341
model_list = get_models()
13371342
if not args in model_list:

Diff for: chatbot/templates/index.html

+117
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@
226226
border-radius: 5px;
227227
padding: 5px;
228228
}
229+
.select-value {
230+
width: calc(100% - 10px);
231+
border: 1px solid #ccc;
232+
border-radius: 5px;
233+
padding: 5px;
234+
}
229235
.leftcolumn {
230236
width: 20px;
231237
}
@@ -308,9 +314,62 @@ <h2 class="popup-title">Session Conversation Thread</h2>
308314
</div>
309315
</div>
310316
</div>
317+
<div class="popup-overlay" id="modelOverlay">
318+
<div class="popup-box" style="width: 90%;">
319+
<h2 class="popup-title">LLM Models</h2>
320+
<div class="popup-content">
321+
<p class="popup-content-text">Use dropdown to select a model to use for the chatbot.</p>
322+
<form id="modelForm">
323+
<div class="popup-table-container">
324+
<table>
325+
<thead>
326+
<tr>
327+
<th class="leftcolumn">Select Model</th>
328+
</tr>
329+
</thead>
330+
<tbody>
331+
<tr>
332+
<td>
333+
<select id="modelSelect" class="select-value">
334+
<option value="gpt2">GPT-2</option>
335+
<option value="gpt3">GPT-3</option>
336+
</select>
337+
</td>
338+
</tr>
339+
<tr>
340+
<td>
341+
<p>
342+
<strong>Current Model:</strong> <span id="currentModel">FPO</span>
343+
</p>
344+
</td>
345+
</tr>
346+
<tr>
347+
<td>
348+
<p>
349+
<strong>List of Models:</strong> <br>
350+
<span id="modelList">FPO</span>
351+
</p>
352+
</td>
353+
</tr>
354+
</tbody>
355+
</table>
356+
</div>
357+
</form>
358+
<div class="popup-divider"></div>
359+
<div class="popup-button-container">
360+
<div id="popup-buttons">
361+
<button type="button" class="dialogue-button" onclick="closeDialogue()">Close</button>
362+
<button type="button" class="dialogue-button" onclick="saveModel()">Save</button>
363+
</div>
364+
</div>
365+
</div>
366+
</div>
367+
</div>
311368
<script>
369+
312370
// Pop-up dialogue functions
313371

372+
// Function to open the settings dialogue
314373
function openDialogue() {
315374
document.getElementById("dialogueOverlay").style.display = "flex";
316375
// First clear the form from any previous settings
@@ -341,18 +400,69 @@ <h2 class="popup-title">Session Conversation Thread</h2>
341400
});
342401
}
343402

403+
// Function to open the model selection dialogue
404+
function openModelDialogue() {
405+
console.log('Opening model dialogue');
406+
document.getElementById("modelOverlay").style.display = "flex";
407+
// First clear the form from any previous settings
408+
const modelSelect = document.getElementById('modelSelect');
409+
modelSelect.innerHTML = '';
410+
// Fetch the current models and populate the form
411+
fetch('/models')
412+
.then(response => response.json())
413+
.then(data => {
414+
const currentModel = getModel();
415+
document.getElementById('currentModel').textContent = currentModel;
416+
// List models as bullets
417+
const modelList = document.getElementById('modelList');
418+
modelList.innerHTML = data.map(model => `<li>${model}</li>`).join('');
419+
// Create options for the models
420+
data.forEach(model => {
421+
const option = document.createElement('option');
422+
option.value = model;
423+
option.textContent = model;
424+
if (model === currentModel) {
425+
option.selected = true;
426+
}
427+
modelSelect.appendChild(option);
428+
});
429+
});
430+
}
431+
432+
// Function to read the footer and extract the model
433+
function getModel() {
434+
const footer = document.getElementById('footer').textContent;
435+
const model = footer.split(' - ')[1];
436+
return model;
437+
}
438+
439+
// Function to save the selected model
440+
function saveModel() {
441+
const modelSelect = document.getElementById('modelSelect');
442+
const selectedModel = modelSelect.value;
443+
console.log('Selected model:', selectedModel);
444+
// Send the selected model to server via socketio message
445+
socket.emit('model', selectedModel);
446+
// Close the dialogue
447+
closeDialogue();
448+
}
449+
450+
// Function to highlight the textarea when changed
344451
function highlightTextarea(textarea) {
345452
textarea.style.backgroundColor = '#ffffcc';
346453
}
347454

455+
// Generic function to close the dialogue
348456
function closeDialogue() {
349457
document.getElementById("dialogueOverlay").style.display = "none";
350458
document.getElementById("debugOverlay").style.display = "none";
459+
document.getElementById("modelOverlay").style.display = "none";
351460
// Set focus back to the text input field
352461
var textInput = document.getElementById("messageInput");
353462
textInput.focus();
354463
}
355464

465+
// Function to reset the settings to the default values
356466
function resetSettings() {
357467
// Verify that the user wants to reset the settings
358468
if (!confirm('Are you sure you want to reset the settings to the default values?')) {
@@ -377,6 +487,7 @@ <h2 class="popup-title">Session Conversation Thread</h2>
377487
});
378488
}
379489

490+
// Function to save the settings to the server
380491
function saveSettings() {
381492
// Combine name and value into a JSON payload
382493
const payload = {};
@@ -632,6 +743,12 @@ <h2 class="popup-title">Session Conversation Thread</h2>
632743
});
633744
}
634745

746+
// Function to handle model dialog request
747+
socket.on('model_dialog', function(data) {
748+
console.log('Received model dialog request');
749+
openModelDialogue();
750+
});
751+
635752
// Function to handle updates received from the server
636753
socket.on('update', function(data) {
637754
const updateContainer = document.getElementById('messageContainer');

Diff for: chatbot/version.py

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

0 commit comments

Comments
 (0)