Chat renaming, JSON lead

Chat renames automatically JSON: lead added to AI messages to force json output

frdel committed Apr 5, 2025 at 21:06 UTC bf43c60d10092ef17504f316938da463fe7fd245
9 files changed +105 -13
agent.py
+1
@@ -371,6 +371,7 @@ class Agent:
371 [
372 SystemMessage(content=system_text),
373 *history_langchain,
374 + AIMessage(content="JSON:"), # force the LLM to start with json
375 ]
376 )
377
models.py
+1 -1
@@ -332,7 +332,7 @@ def get_openrouter_chat(
332 dotenv.get_dotenv_value("OPEN_ROUTER_BASE_URL")
333 or "https://openrouter.ai/api/v1"
334 )
335 - return ChatOpenAI(api_key=api_key, model=model_name, base_url=base_url, **kwargs) # type: ignore
335 + return ChatOpenAI(api_key=api_key, model=model_name, base_url=base_url, stream_usage=True, **kwargs) # type: ignore
336
337
338 def get_openrouter_embedding(
prompts/default/fw.rename_chat.msg.md new
+8
@@ -0,0 +1,8 @@
1 +# Instruction
2 +- provide a chat name for the following
3 +
4 +# Current chat name
5 +{{current_name}}
6 +
7 +# Chat history
8 +{{history}}
prompts/default/fw.rename_chat.sys.md new
+19
@@ -0,0 +1,19 @@
1 +# AI role
2 +- You are a chat naming assistant
3 +- Your role is to suggest a short chat name for the current conversation
4 +
5 +# Input
6 +- You are given the current chat name and current chat history
7 +
8 +# Output
9 +- Respond with a short chat name (1-3 words) based on the chat history
10 +- Consider current chat name and only change it when the conversation topic has changed
11 +- Focus mainly on the end of the conversation history, there you can detect if the topic has changed
12 +- Only respond with the chat name without any formatting, intro or additional text
13 +- Maintain proper capitalization
14 +
15 +# Example responses
16 +Database setup
17 +Requirements installation
18 +Merging documents
19 +Image analysis
\ No newline at end of file
python/api/poll.py
+1
@@ -19,6 +19,7 @@ class Poll(ApiHandler):
19 ctxs.append(
20 {
21 "id": ctx.id,
22 + "name": ctx.name,
23 "no": ctx.no,
24 "log_guid": ctx.log.guid,
25 "log_version": len(ctx.log.updates),
python/extensions/monologue_end/_60_rename_chat.py new
+37
@@ -0,0 +1,37 @@
1 +from python.helpers import persist_chat, tokens
2 +from python.helpers.extension import Extension
3 +from agent import LoopData
4 +import asyncio
5 +
6 +
7 +class RenameChat(Extension):
8 +
9 + async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
10 + asyncio.create_task(self.change_name())
11 +
12 + async def change_name(self):
13 + try:
14 + # prepare history
15 + history_text = self.agent.history.output_text()
16 + ctx_length = int(self.agent.config.utility_model.ctx_length * 0.7)
17 + history_text = tokens.trim_to_tokens(history_text, ctx_length, "start")
18 + # prepare system and user prompt
19 + system = self.agent.read_prompt("fw.rename_chat.sys.md")
20 + current_name = self.agent.context.name
21 + message = self.agent.read_prompt(
22 + "fw.rename_chat.msg.md", current_name=current_name, history=history_text
23 + )
24 + # call utility model
25 + new_name = await self.agent.call_utility_model(
26 + system=system, message=message, background=True
27 + )
28 + # update name
29 + if new_name:
30 + # trim name to max length if needed
31 + if len(new_name) > 40:
32 + new_name = new_name[:40] + "..."
33 + # apply to context and save
34 + self.agent.context.name = new_name
35 + persist_chat.save_tmp_chat(self.agent.context)
36 + except Exception as e:
37 + pass # non-critical
python/helpers/persist_chat.py
+1
@@ -89,6 +89,7 @@ def _serialize_context(context: AgentContext):
89
90 return {
91 "id": context.id,
92 + "name": context.name,
93 "agents": agents,
94 "streaming_agent": (
95 context.streaming_agent.number if context.streaming_agent else 0
python/helpers/tokens.py
+36 -11
@@ -1,19 +1,44 @@
1 +from typing import Literal
2 import tiktoken
3
4 APPROX_BUFFER = 1.1
5 +TRIM_BUFFER = 0.8
6 +
7
8 def count_tokens(text: str, encoding_name="cl100k_base") -> int:
6 - if not text:
7 - return 0
8 -
9 - # Get the encoding
10 - encoding = tiktoken.get_encoding(encoding_name)
9 + if not text:
10 + return 0
11 +
12 + # Get the encoding
13 + encoding = tiktoken.get_encoding(encoding_name)
14 +
15 + # Encode the text and count the tokens
16 + tokens = encoding.encode(text)
17 + token_count = len(tokens)
18 +
19 + return token_count
20 +
21 +
22 +def approximate_tokens(
23 + text: str,
24 +) -> int:
25 + return int(count_tokens(text) * APPROX_BUFFER)
26 +
27 +
28 +def trim_to_tokens(
29 + text: str,
30 + max_tokens: int,
31 + direction: Literal["start", "end"],
32 + ellipsis: str = "...",
33 +) -> str:
34 + chars = len(text)
35 + tokens = count_tokens(text)
36
12 - # Encode the text and count the tokens
13 - tokens = encoding.encode(text)
14 - token_count = len(tokens)
37 + if tokens <= max_tokens:
38 + return text
39
16 - return token_count
40 + approx_chars = int(chars * (max_tokens / tokens) * TRIM_BUFFER)
41
18 -def approximate_tokens(text: str, ) -> int:
19 - return int(count_tokens(text) * APPROX_BUFFER)
\ No newline at end of file
42 + if direction == "start":
43 + return text[:approx_chars] + ellipsis
44 + return ellipsis + text[chars - approx_chars : chars]
webui/index.html
+1 -1
@@ -97,7 +97,7 @@
97 <li>
98 <span :class="{'chat-list-button': true, 'font-bold': context.id === selected}"
99 @click="selected = context.id; selectChat(context.id)">
100 - Chat #<span x-text="context.no"></span>
100 + <span x-text="context.name ? context.name : 'Chat #' + context.no"></span>
101 </span>
102 <button class="edit-button" @click="killChat(context.id)">X</button>
103 </li>