aboutsummaryrefslogtreecommitdiffstats
path: root/assistant.py
diff options
context:
space:
mode:
Diffstat (limited to 'assistant.py')
-rw-r--r--assistant.py44
1 files changed, 43 insertions, 1 deletions
diff --git a/assistant.py b/assistant.py
index f246101..cf92c16 100644
--- a/assistant.py
+++ b/assistant.py
@@ -244,6 +244,8 @@ PAGE_PX = 1600 # longest side of a page render
MAX_AUDIO_S = 60
HEAR = ("Transcribe this audio exactly, in the language spoken (Italian or English). "
"Output only the transcript.")
+TITLE = ("Write a title of at most six words for this conversation, in the language "
+ "it is in. Output only the title.")
class Refused(Exception):
@@ -1027,13 +1029,38 @@ def answer(mine, text, files=()):
if turn != mine:
log("interrupted")
return
+ fresh = cur["id"] is None
cur["id"] = save_turn(DB, cur["id"], text, said, used, entry, chips)
+ conv = cur["id"]
cur["history"].append(entry)
cur["turns"].append({"user": text, "reply": said, "tools": used, "files": chips})
cur["pending"] = None
emit({"type": "done", "id": cur["id"]})
emit({"type": "state", "state": "idle"})
log("assistant:", said)
+ if fresh:
+ retitle(mine, conv, text, said)
+
+
+def retitle(mine, conv, user, said):
+ # The first six words stand until the model names the conversation. Asked
+ # after done, under the reply's turn: the next ask, stop, new or open
+ # hangs it up, so it never holds a reply back, and the six words stay.
+ try:
+ msgs = [{"role": "user",
+ "content": f"{TITLE}\n\nUser: {user[:2000]}\n\nAssistant: {said[:2000]}"}]
+ text = "".join(p for k, p in ask(msgs, {}, None, mine, think=False, meter=False)
+ if k == "token")
+ line = next((l for l in text.splitlines() if l.strip()), "")
+ title = line.strip().strip("\"'*#").strip()[:80]
+ with lock:
+ if turn != mine or not title:
+ return
+ with DB:
+ DB.execute("UPDATE conversations SET title = ? WHERE id = ?", (title, conv))
+ log(f"titled: {title}")
+ except Exception: # the reply is saved; a title is not worth an error
+ log("title failed:\n" + traceback.format_exc().rstrip())
def main():
@@ -1290,7 +1317,15 @@ def test():
self.wfile.write(json.dumps(body).encode())
def do_POST(self):
- script["body"] = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
+ body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
+ if str(body["messages"][0]["content"]).startswith(TITLE):
+ # Answered apart, so a title never takes a reply meant for an ask.
+ self.send_response(200)
+ self.end_headers()
+ self.wfile.write(b"data: " + json.dumps({"choices": [{"delta": {
+ "content": script.get("title", "")}}]}).encode() + b"\n\ndata: [DONE]\n\n")
+ return
+ script["body"] = body
script.get("bodies", []).append(script["body"])
if script.get("hold"):
# A long prompt pass: nothing is sent, so only the client
@@ -1354,6 +1389,7 @@ def test():
{"tool_calls": [{"index": 0, "id": "c1", "function": {"name": "now",
"arguments": "{}"}}]}],
[{"content": "It is "}, {"content": "noon."}]]
+ script["title"] = '"Ora attuale"\n'
send(type="ask", text="what time is it")
got = [(e["type"], e.get("delta") or e.get("name") or e.get("state") or e.get("text"))
for e in until("done") if e["type"] != "ctx"]
@@ -1364,6 +1400,12 @@ def test():
assert until("state")[-1]["state"] == "idle"
assert script["body"]["chat_template_kwargs"]["enable_thinking"] is True
conv = cur["id"]
+ for _ in range(40): # the title comes after done
+ if list_convs(DB)[0]["title"] == "Ora attuale":
+ break
+ time.sleep(0.05)
+ assert list_convs(DB)[0]["title"] == "Ora attuale", list_convs(DB)
+ script["title"] = ""
assert load_conv(DB, conv)[1] == [{"user": "what time is it", "reply": "It is noon.",
"tools": ["now"], "files": []}]
# The tool result went to the model but history keeps a placeholder.