Some stuff and that

This commit is contained in:
John Burwell 2025-10-19 17:19:00 +00:00
parent d3cedd9b2d
commit b170d1fa5d
4 changed files with 218 additions and 3 deletions

View File

@ -34,6 +34,14 @@ The Chat plugin supports the following configuration parameters:
- **`passive_cooldown`**: Cooldown in seconds after ending a passive thread before starting a new one. Default: `120`.
- **`passive_trigger_words`**: Space-separated keywords that increase the chance of a passive response in `smart` mode. Default: *(empty)*.
- **`passive_prompt_addendum`**: Text appended to the system prompt while passive mode is active, shaping the bot's etiquette.
- **`history_service_url`**: Base URL for the optional history service (e.g. `http://127.0.0.1:8901`). Leave blank to disable.
- **`history_service_token`**: Bearer token to send with history service requests (if required).
- **`history_service_timeout`**: Timeout in seconds for history service HTTP requests. Default: `1.5`.
- **`history_include_files`**: Number of rotated log files the service should scan (`include_files` parameter). Default: `2`.
- **`history_result_limit`**: Maximum number of log lines to request from the service. Default: `60`.
- **`history_max_chars`**: Maximum characters of history context injected into the prompt. Default: `1800`.
- **`history_max_lines`**: Maximum history lines injected into the prompt. Default: `80`.
- **`history_trigger_words`**: Words/phrases that cause the bot to consult the history service. Default: `remember earlier history logs recap summary yesterday before`.
### Example Configuration
@ -68,6 +76,21 @@ For a looser "hang out" presence, activate `smart` mode and adjust the heuristic
In smart mode the bot watches channel flow, but only jumps in when it is confident it can help or close an active thread. Direct `.chat`/`@Bot chat` commands still work exactly as before.
### History Service
Point the plugin at the local history API and let it pull in context when users ask for recaps:
```
/msg BotName config plugins.Chat.history_service_url http://127.0.0.1:8901
/msg BotName config plugins.Chat.history_trigger_words "remember earlier recap"
```
If the service expects a bearer token:
```
/msg BotName config plugins.Chat.history_service_token YOURTOKEN
```
When a `.chat` request (or passive interjection) contains one of the trigger phrases—or starts with `history:`/`log:`—the plugin calls the service, pulls any matching log lines, and appends a short “Recent channel facts” block to the OpenAI prompt before generating the final reply.
## Usage
Once configured, you can use the `chat` command to interact with the bot. For example:

View File

@ -185,6 +185,80 @@ conf.registerGlobalValue(
)
)
# History service integration
conf.registerGlobalValue(
Chat,
"history_service_url",
registry.String(
"http://127.0.0.1:8901",
_("""Base URL for the optional history service (e.g. http://127.0.0.1:8901). Leave blank to disable."""),
)
)
conf.registerGlobalValue(
Chat,
"history_service_token",
registry.String(
"",
_("""Bearer token to authenticate with the history service (if required)."""),
private=True,
)
)
conf.registerGlobalValue(
Chat,
"history_service_timeout",
registry.Float(
1.5,
_("""Timeout in seconds for history service HTTP requests."""),
)
)
conf.registerGlobalValue(
Chat,
"history_include_files",
registry.Integer(
2,
_("""How many rotated log files the history service should scan (passed as include_files parameter)."""),
)
)
conf.registerGlobalValue(
Chat,
"history_result_limit",
registry.Integer(
60,
_("""Maximum number of log lines to request from the history service."""),
)
)
conf.registerGlobalValue(
Chat,
"history_max_chars",
registry.Integer(
1800,
_("""Maximum number of characters of history context to include in the prompt."""),
)
)
conf.registerGlobalValue(
Chat,
"history_max_lines",
registry.Integer(
80,
_("""Maximum number of history lines to include in the prompt."""),
)
)
conf.registerGlobalValue(
Chat,
"history_trigger_words",
registry.SpaceSeparatedListOfStrings(
["remember", "earlier", "history", "logs", "recap", "summary", "yesterday", "before"],
_("""Words or phrases that should cause the bot to consult the history service."""),
)
)
# Logging level for the plugin
conf.registerGlobalValue(
Chat,

120
plugin.py
View File

@ -260,6 +260,115 @@ class Chat(callbacks.Plugin):
rendered.append({"role": event["role"], "content": content})
return rendered
def _history_enabled(self):
return bool(self.registryValue("history_service_url").strip())
def _extract_history_query(self, text):
lowered = text.lower()
prefixes = ["history:", "log:", "logs:", "recap:"]
for prefix in prefixes:
if lowered.startswith(prefix):
query = text[len(prefix):].strip()
return True, query or None
triggers = [t.lower() for t in self.registryValue("history_trigger_words") if t]
for trigger in triggers:
if trigger and trigger in lowered:
return True, text
phrase_triggers = ["what did", "when did", "who said", "last time", "earlier today"]
for phrase in phrase_triggers:
if phrase in lowered:
return True, text
return False, None
def _history_request(self, irc, channel, query=None):
base_url = self.registryValue("history_service_url").strip()
if not base_url:
return []
base_url = base_url.rstrip('/')
endpoint = "/search" if query else "/recent"
params = {
"network": irc.network,
"channel": channel,
"limit": str(max(1, self.registryValue("history_result_limit"))),
}
include_files = self.registryValue("history_include_files")
if include_files > 0:
params["include_files"] = str(include_files)
if query:
params["q"] = query[:240]
headers = {}
token = self.registryValue("history_service_token").strip()
if token:
headers["Authorization"] = f"Bearer {token}"
timeout = self.registryValue("history_service_timeout")
url = f"{base_url}{endpoint}"
try:
response = requests.get(url, params=params, headers=headers, timeout=timeout)
if response.status_code == 404:
return []
response.raise_for_status()
data = response.json()
if isinstance(data, list):
return data
return []
except requests.RequestException as e:
self.log.debug("History request failed | url=%s | error=%s", url, e)
return []
def _format_history_block(self, irc, items):
max_chars = max(0, self.registryValue("history_max_chars"))
max_lines = max(1, self.registryValue("history_max_lines"))
lines = []
seen = set()
for item in items:
ts = (item.get("ts") or "").strip()
nick = (item.get("nick") or "").strip()
text = (item.get("text") or "").strip()
if not text:
continue
if nick and ircutils.strEqual(nick, irc.nick):
continue
fragment = f"{ts} {nick}: {text}".strip()
if fragment in seen:
continue
seen.add(fragment)
lines.append(fragment)
if len(lines) >= max_lines:
break
buffer = []
used = 0
for line in lines:
delta = len(line) + (1 if buffer else 0)
if max_chars and used + delta > max_chars:
break
buffer.append(line)
used += delta
if not buffer:
return None
return "Recent channel facts:\n" + "\n".join(buffer)
def _maybe_add_history_context(self, irc, msg, messages, events, user_text):
if not user_text or not self._history_enabled():
return messages
should_query, query = self._extract_history_query(user_text)
if not should_query:
return messages
items = self._history_request(irc, msg.args[0], query)
if not items and query:
items = self._history_request(irc, msg.args[0], None)
if not items:
self.log.debug("History lookup returned no items | channel=%s", msg.args[0])
return messages
block = self._format_history_block(irc, items)
if not block:
return messages
insert_at = len(messages) - 1 if messages else 0
if insert_at < 0:
insert_at = 0
messages.insert(insert_at, {"role": "system", "content": block})
self.log.debug("History context appended | channel=%s | lines=%d", msg.args[0], block.count('\n'))
return messages
def _build_messages(self, irc, msg, user_content, include_current=False):
invocation_string = self._invocation_string(irc)
events = self._collect_events(
@ -518,6 +627,7 @@ class Chat(callbacks.Plugin):
self._reset_if_stale(session, now)
messages, events = self._build_messages(irc, msg, text, include_current=True)
self.log.debug("Passive base context: %s", json.dumps(messages))
if not self._should_respond_passively(irc, msg, session, events):
return
@ -535,6 +645,10 @@ class Chat(callbacks.Plugin):
)
return
messages = self._maybe_add_history_context(irc, msg, messages, events, text)
messages = truncate_messages(messages, 8192)
self.log.debug("Passive API Request: %s", json.dumps(messages))
try:
response = self._post_chat_completion(messages, max_tokens=self.registryValue("max_tokens"))
except (ValueError, requests.exceptions.Timeout, requests.exceptions.HTTPError) as e:
@ -575,7 +689,11 @@ class Chat(callbacks.Plugin):
session.state = EngagementState.INVITED
session.replies_in_thread = 0
messages, _ = self._build_messages(irc, msg, string, include_current=False)
messages, events = self._build_messages(irc, msg, string, include_current=False)
self.log.debug(f"API base context: {json.dumps(messages)}")
messages = self._maybe_add_history_context(irc, msg, messages, events, string)
messages = truncate_messages(messages, 8192)
self.log.debug(f"API Request: {json.dumps(messages)}")
try:

View File

@ -1,7 +1,8 @@
You are an IRC bot named $bot_name in channel $channel_name.
IRC style rules:
- Keep replies short, casual, and lowercase when possible.
- Answer questions helpfully.
- Keep replies short and lowercase when possible.
- Prefer to fit your entire reply on a single line.
- You may use 23 short lines, like a human typing follow-ups. Never more.
- If you write poetry, haiku, or song lyrics, collapse them into a single line joined with space-slash-space ( / ) or space-pipe-space ( | ) between verses, instead of sending multiple lines.
@ -10,5 +11,4 @@ IRC style rules:
- A /me action should always begin with /me and only /me. Do not include any formatting.
- You may use classic emoticons: :) :( :D D: :'( etc.
- Avoid using emoji.
- Match the channel vibe: technical when asked, playful when social.
- Do not reveal that you are an AI or bot; stay in character as a normal IRC user.