Llama-cpp inference, database stored models
This commit is contained in:
1 parent
a3700ca15a
commit
f2a6fc0cd5
11 files changed
+192
-6
No files matched your search
+2
-1
@@ -4,4 +4,5 @@ __pycache__
|
|||||||
horde.db
|
horde.db
|
||||||
|
|
||||||
/test-data
|
/test-data
|
||||||
/local-data
|
/local-data
|
||||||
|
/models
|
||||||
@@ -6,4 +6,5 @@ nest_asyncio
|
|||||||
replicate
|
replicate
|
||||||
qdrant-client[fastembed]
|
qdrant-client[fastembed]
|
||||||
ollama
|
ollama
|
||||||
tortoise-orm
|
tortoise-orm
|
||||||
|
llama-cpp-python
|
||||||
@@ -19,7 +19,7 @@ class ExpertAgent(Agent):
|
|||||||
description = CLASS_DESCRIPTION
|
description = CLASS_DESCRIPTION
|
||||||
schema_fields = [
|
schema_fields = [
|
||||||
AgentField('system_prompt', 'string', default="You are an expert AI assistant."),
|
AgentField('system_prompt', 'string', default="You are an expert AI assistant."),
|
||||||
AgentField('model', 'string', default="qwen-big"),
|
AgentField('model', 'model_id', default=None),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, app, data):
|
def __init__(self, app, data):
|
||||||
@@ -51,7 +51,15 @@ class ExpertAgent(Agent):
|
|||||||
|
|
||||||
model = self.config['model']
|
model = self.config['model']
|
||||||
|
|
||||||
if model.startswith("ollama:"):
|
if type(model) is int:
|
||||||
|
db_model = await self.app.get_model(model)
|
||||||
|
|
||||||
|
if db_model.value["engine"] == "llama-cpp":
|
||||||
|
response = await inference.local_chat_completion(self.app, messages, model=db_model.value['model'])
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown model: " + repr(model))
|
||||||
|
|
||||||
|
elif model.startswith("ollama:"):
|
||||||
response = await inference.ollama_chat_completion(self.app, messages, model=model[7:])
|
response = await inference.ollama_chat_completion(self.app, messages, model=model[7:])
|
||||||
elif model == "qwen-big":
|
elif model == "qwen-big":
|
||||||
response = await inference.replicate_qwen_completion(self.app, messages, model=inference.Models.qwen3_235b)
|
response = await inference.replicate_qwen_completion(self.app, messages, model=inference.Models.qwen3_235b)
|
||||||
|
|||||||
+23
-1
@@ -244,10 +244,32 @@ class App(ChatMixin):
|
|||||||
agent_id=agent_id,
|
agent_id=agent_id,
|
||||||
kind=kind,
|
kind=kind,
|
||||||
key=key,
|
key=key,
|
||||||
defaults={"value": value}
|
value=value,
|
||||||
)
|
)
|
||||||
print(f"Saved agent resource: {agent_id} - {kind} - {key}")
|
print(f"Saved agent resource: {agent_id} - {kind} - {key}")
|
||||||
|
|
||||||
|
async def get_models(self, kind=None):
|
||||||
|
return await db.Model.all()
|
||||||
|
async def get_model(self, model_id):
|
||||||
|
return await db.Model.get(id=model_id)
|
||||||
|
|
||||||
|
async def save_model(self, model_id, kind, name, value):
|
||||||
|
if model_id is None:
|
||||||
|
model = await db.Model.create(
|
||||||
|
name=name,
|
||||||
|
kind=kind,
|
||||||
|
value=value,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
model = await db.Model.update_or_create(
|
||||||
|
id=model_id,
|
||||||
|
name=name,
|
||||||
|
kind=kind,
|
||||||
|
value=value,
|
||||||
|
)
|
||||||
|
print(f"Saved model: {model.id} - {name}")
|
||||||
|
return model
|
||||||
|
|
||||||
# --- Main Entry Point ---
|
# --- Main Entry Point ---
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
|||||||
@@ -128,6 +128,11 @@ class MessageDatum(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
table = "message_data"
|
table = "message_data"
|
||||||
|
|
||||||
|
class Model(models.Model):
|
||||||
|
id = fields.IntField(pk=True)
|
||||||
|
name = fields.CharField(max_length=255)
|
||||||
|
kind = fields.CharField(max_length=50)
|
||||||
|
value = fields.JSONField(null=True)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
class User(Base):
|
class User(Base):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import threading
|
|||||||
import ollama
|
import ollama
|
||||||
from stream import Stream
|
from stream import Stream
|
||||||
|
|
||||||
|
from llama_cpp import Llama
|
||||||
|
|
||||||
VALID_ROLES = ['user', 'assistant', 'system']
|
VALID_ROLES = ['user', 'assistant', 'system']
|
||||||
|
|
||||||
@@ -19,6 +20,27 @@ class Models:
|
|||||||
qwen3_8b = "lucataco/qwen3-vl-8b-instruct:39e893666996acf464cff75688ad49ac95ef54e9f1c688fbc677330acc478e11"
|
qwen3_8b = "lucataco/qwen3-vl-8b-instruct:39e893666996acf464cff75688ad49ac95ef54e9f1c688fbc677330acc478e11"
|
||||||
qwen3_235b = "qwen/qwen3-235b-a22b-instruct-2507"
|
qwen3_235b = "qwen/qwen3-235b-a22b-instruct-2507"
|
||||||
|
|
||||||
|
# llama cpp models
|
||||||
|
# stores a single model
|
||||||
|
class ModelCache:
|
||||||
|
last_used = None
|
||||||
|
last_model = None
|
||||||
|
|
||||||
|
# llama.cpp
|
||||||
|
async def local_chat_completion(app, messages, model, opts={}):
|
||||||
|
llm = Llama(
|
||||||
|
model_path=f"{app.data_directory}/models/{model}",
|
||||||
|
# n_gpu_layers=-1, # Uncomment to use GPU acceleration
|
||||||
|
# seed=1337, # Uncomment to set a specific seed
|
||||||
|
# n_ctx=2048, # Uncomment to increase the context window
|
||||||
|
)
|
||||||
|
response = llm.create_chat_completion(
|
||||||
|
messages = [{'role': msg.role, 'content': msg.content} for msg in messages]
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: KeyError: 'text'
|
||||||
|
return response["choices"][0]["text"]
|
||||||
|
|
||||||
async def ollama_list(app, messages, model, opts={}):
|
async def ollama_list(app, messages, model, opts={}):
|
||||||
# TODO: test the output, and use this
|
# TODO: test the output, and use this
|
||||||
return ollama.list()
|
return ollama.list()
|
||||||
@@ -42,6 +64,8 @@ async def ollama_chat_completion(app, messages, model, opts={}):
|
|||||||
print("END OLLAMA INFERENCE")
|
print("END OLLAMA INFERENCE")
|
||||||
return await content_stream.content_wait()
|
return await content_stream.content_wait()
|
||||||
|
|
||||||
|
#return content_stream
|
||||||
|
|
||||||
|
|
||||||
async def replicate_llama_completion(app, messages, model=None, opts={}):
|
async def replicate_llama_completion(app, messages, model=None, opts={}):
|
||||||
if model is None:
|
if model is None:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ def start_server(app):
|
|||||||
|
|
||||||
# These paths must match the routes in the frontend SPA
|
# These paths must match the routes in the frontend SPA
|
||||||
# file: frontend/src/routes/index.js
|
# file: frontend/src/routes/index.js
|
||||||
|
@server.route('/models')
|
||||||
@server.route('/chat')
|
@server.route('/chat')
|
||||||
@server.route('/agent')
|
@server.route('/agent')
|
||||||
@server.route('/chat/<path:subpath>')
|
@server.route('/chat/<path:subpath>')
|
||||||
@@ -167,6 +168,21 @@ def start_server(app):
|
|||||||
)
|
)
|
||||||
await send({'message': "agent updated", 'agentId': agent_id, 'nonce': nonce})
|
await send({'message': "agent updated", 'agentId': agent_id, 'nonce': nonce})
|
||||||
|
|
||||||
|
elif action == "get-models":
|
||||||
|
await send({
|
||||||
|
'models': [{"id": model.id, "name": model.name} for model in await app.get_models()],
|
||||||
|
'nonce': nonce
|
||||||
|
})
|
||||||
|
|
||||||
|
elif action == "save-model":
|
||||||
|
model = await app.save_model(
|
||||||
|
model_id=body.get("modelId"),
|
||||||
|
name=body["name"],
|
||||||
|
kind=body["kind"],
|
||||||
|
value=body["value"],
|
||||||
|
)
|
||||||
|
await send({'message': "model saved", 'modelId': model.id, 'nonce': nonce})
|
||||||
|
|
||||||
elif action == "get-agent-config-fields":
|
elif action == "get-agent-config-fields":
|
||||||
await send({'fields': await app.get_agent_config_fields(body['agentId']), 'nonce': nonce})
|
await send({'fields': await app.get_agent_config_fields(body['agentId']), 'nonce': nonce})
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
<nav v-if="mode=='agents'">
|
<nav v-if="mode=='agents'">
|
||||||
<RouterLink v-for="it in agents" :to="'/agent/' + it.id" class="nav-item">{{it.name}}</RouterLink>
|
<RouterLink v-for="it in agents" :to="'/agent/' + it.id" class="nav-item">{{it.name}}</RouterLink>
|
||||||
<RouterLink to="/agent/create" class="nav-item">Create Agent</RouterLink>
|
<RouterLink to="/agent/create" class="nav-item">Create Agent</RouterLink>
|
||||||
|
<RouterLink to="/models" class="nav-item">Manage Models</RouterLink>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ const router = createRouter({
|
|||||||
path: `/agent/create`,
|
path: `/agent/create`,
|
||||||
component: () => import('../views/AgentCreate.vue')
|
component: () => import('../views/AgentCreate.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'models',
|
||||||
|
path: `/models`,
|
||||||
|
component: () => import('../views/Models.vue')
|
||||||
|
},
|
||||||
objectView('chat', () => import('../views/Chat.vue')),
|
objectView('chat', () => import('../views/Chat.vue')),
|
||||||
objectView('agent', () => import('../views/Agent.vue')),
|
objectView('agent', () => import('../views/Agent.vue')),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
const configFields = ref([]);
|
const configFields = ref([]);
|
||||||
// Only used for scripted agents.
|
// Only used for scripted agents.
|
||||||
const actionDefinitions = ref([]);
|
const actionDefinitions = ref([]);
|
||||||
|
const modelList = ref([]);
|
||||||
|
|
||||||
|
|
||||||
// === State Management === //
|
// === State Management === //
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
name: field.name,
|
name: field.name,
|
||||||
type: field.type,
|
type: field.type,
|
||||||
value: field.value,
|
value: field.value,
|
||||||
|
kind: field.type,
|
||||||
default: null,
|
default: null,
|
||||||
enabled: field.value != null,
|
enabled: field.value != null,
|
||||||
}));
|
}));
|
||||||
@@ -72,6 +74,11 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadModels () {
|
||||||
|
let res = await backend.sendAction("get-models");
|
||||||
|
modelList.value = res.models;
|
||||||
|
}
|
||||||
|
|
||||||
// This won't fire when immediately, the mounted callback will take care
|
// This won't fire when immediately, the mounted callback will take care
|
||||||
watch(() => props.id, newId => {
|
watch(() => props.id, newId => {
|
||||||
loadAgentData(newId);
|
loadAgentData(newId);
|
||||||
@@ -79,6 +86,7 @@
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loadAgentData(props.id);
|
loadAgentData(props.id);
|
||||||
|
loadModels();
|
||||||
});
|
});
|
||||||
|
|
||||||
// === Prompt === //
|
// === Prompt === //
|
||||||
@@ -336,7 +344,11 @@
|
|||||||
<span class="label">{{ configField.name }}</span>
|
<span class="label">{{ configField.name }}</span>
|
||||||
<input type="checkbox" v-model="configField.enabled" />
|
<input type="checkbox" v-model="configField.enabled" />
|
||||||
<br>
|
<br>
|
||||||
<textarea v-model="configField.value" class="config-input" :disabled="!configField.enabled"/>
|
<select v-if="configField.kind == 'model_id'" v-model="configField.value">
|
||||||
|
<option v-if="!modelList.length" disabled>empty</option>
|
||||||
|
<option :value="model.id" v-for="model in modelList">{{ model.name }}</option>
|
||||||
|
</select>
|
||||||
|
<textarea v-else v-model="configField.value" class="config-input" :disabled="!configField.enabled"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" @click="saveChanges()">Save</button>
|
<button type="submit" @click="saveChanges()">Save</button>
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
ref, computed, reactive,
|
||||||
|
onMounted, onUnmounted,
|
||||||
|
} from "vue";
|
||||||
|
|
||||||
|
import TitleBar from '../components/TitleBar.vue';
|
||||||
|
|
||||||
|
import * as backend from '../backend';
|
||||||
|
import * as ui from '../ui';
|
||||||
|
|
||||||
|
const models = ref([]);
|
||||||
|
const createExpanded = ref(false);
|
||||||
|
|
||||||
|
const editModel = ref({kind: 'text', name: null, inputValue: '{}'});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
let res = await backend.sendAction("get-models");
|
||||||
|
models.value = res.models;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createModel () {
|
||||||
|
let current = Object.assign({}, editModel.value);
|
||||||
|
|
||||||
|
if (!current.name) {
|
||||||
|
ui.alert("Missing name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
current.value = JSON.parse(current.inputValue);
|
||||||
|
delete current.inputValue;
|
||||||
|
} catch (exception) {
|
||||||
|
ui.alert("Bad value: " + exception.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await backend.sendAction("save-model", current);
|
||||||
|
if (res.modelId) {
|
||||||
|
current.id = res.modelId;
|
||||||
|
models.value.push(current)
|
||||||
|
} else {
|
||||||
|
ui.alert("bad response: " + JSON.stringify(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewModel (model) {
|
||||||
|
alert(JSON.stringify(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteModel (model) {
|
||||||
|
alert("TODO: delete Model. ID: " + model.id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
<TitleBar>
|
||||||
|
<h1>Models</h1>
|
||||||
|
</TitleBar>
|
||||||
|
|
||||||
|
<div class="entry" v-for="model in models">
|
||||||
|
{{ model.name }}
|
||||||
|
<button @click="viewModel(model)">View</button>
|
||||||
|
<button @click="deleteModel(model)">Delete</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-container">
|
||||||
|
<template v-if="createExpanded">
|
||||||
|
<p>Kinds is only text for now. Value must be like:</p>
|
||||||
|
<pre>{}</pre>
|
||||||
|
<input type="text" v-model="editModel.kind" placeholder="kind">
|
||||||
|
<input type="text" v-model="editModel.name" placeholder="name">
|
||||||
|
<textarea v-model="editModel.inputValue" placeholder="{}"></textarea>
|
||||||
|
<div>
|
||||||
|
<button @click="createModel()">Create</button>
|
||||||
|
<button @click="createExpanded=false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<button @click="createExpanded=true" v-else>Create Model</button>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.entry {}
|
||||||
|
</style>
|
||||||
Reference in new issue
Block a user