major changes to base
This commit is contained in:
parent
a34105761b
commit
4c3a5bad02
7 changed files with 269 additions and 5 deletions
|
|
@ -1,3 +1,74 @@
|
|||
"""
|
||||
Core base class for all listeners in the organism.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
from lxml import etree
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XMLListener:
|
||||
"""Stub for static analysis — real class is in agentserver.listeners.base"""
|
||||
pass
|
||||
"""
|
||||
Base class for all capabilities (personalities, tools, gateways).
|
||||
|
||||
Subclasses must:
|
||||
- Define `listens_to` (list of root tags they handle)
|
||||
- Implement `async handle()`
|
||||
|
||||
The `convo_id` received in handle() MUST be preserved in any response payload.
|
||||
"""
|
||||
|
||||
listens_to: List[str] = [] # Must be overridden in subclass
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.name = name or self.__class__.__name__
|
||||
self.config = config or {}
|
||||
self.logger = logging.getLogger(f"{__name__}.{self.name}")
|
||||
|
||||
async def handle(
|
||||
self, msg: etree.Element, convo_id: str
|
||||
) -> Optional[etree.Element]:
|
||||
"""
|
||||
Handle a message whose root tag matches this listener.
|
||||
|
||||
Args:
|
||||
msg: The payload element (repaired and C14N'd)
|
||||
convo_id: Thread/conversation UUID — must be preserved in response
|
||||
|
||||
Returns:
|
||||
Response payload element with the same convo_id, or None
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
f"{self.__class__.__name__}.handle() must be implemented"
|
||||
)
|
||||
|
||||
def make_response(
|
||||
self,
|
||||
tag: str,
|
||||
text: Optional[str] = None,
|
||||
*,
|
||||
convo_id: str,
|
||||
**attribs,
|
||||
) -> etree.Element:
|
||||
"""
|
||||
Helper to create a response element with a preserved convo_id attribute.
|
||||
Recommended for all listeners.
|
||||
"""
|
||||
elem = etree.Element(tag, convo_id=convo_id, **attribs)
|
||||
if text is not None:
|
||||
elem.text = text
|
||||
return elem
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__} listens_to={self.listens_to}>"
|
||||
104
agentserver/listeners/base_llm.py
Normal file
104
agentserver/listeners/base_llm.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
"""
|
||||
Base class for all LLM-powered personalities.
|
||||
|
||||
Enforces:
|
||||
- Immutable no-paperclippers manifesto as first system message
|
||||
- Owner-provided personality prompt
|
||||
- Strict response template instructions
|
||||
- Per-conversation history
|
||||
- Direct use of the centralized LLMConnectionPool
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Dict, List
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from agentserver.listeners.base import XMLListener
|
||||
from agentserver.llm_connection import llm_pool
|
||||
from agentserver.prompts.no_paperclippers import MANIFESTO_MESSAGE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LLMPersonality(XMLListener):
|
||||
"""
|
||||
Abstract base for all LLM-based listeners.
|
||||
Concrete subclasses only provide listens_to and personality prompt.
|
||||
"""
|
||||
|
||||
# Subclasses must set this
|
||||
listens_to: List[str] = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
personality_message: dict,
|
||||
response_template: str,
|
||||
model: str = "grok-4",
|
||||
temperature: float = 0.7,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.personality_message = personality_message
|
||||
self.response_template = response_template
|
||||
self.model = model
|
||||
self.temperature = temperature
|
||||
|
||||
# v1: strict instructions via prompt only (schema validation coming later)
|
||||
self.output_instructions = {
|
||||
"role": "system",
|
||||
"content": (
|
||||
"You MUST respond using EXACTLY this XML structure. "
|
||||
"Replace {{convo_id}} and {{response_text}} with the correct values. "
|
||||
"Never add extra tags, attributes, or text outside this structure. "
|
||||
"Use <![CDATA[...]]> if your response contains <, >, or &.\n\n"
|
||||
f"{response_template.strip()}"
|
||||
)
|
||||
}
|
||||
|
||||
self.conversations: Dict[str, List[dict]] = {}
|
||||
|
||||
def _build_messages(self, convo_id: str) -> List[dict]:
|
||||
history = self.conversations.get(convo_id, [])
|
||||
return [
|
||||
MANIFESTO_MESSAGE, # immutable safety
|
||||
self.personality_message, # flavor
|
||||
self.output_instructions, # strict format
|
||||
] + history
|
||||
|
||||
async def handle(self, msg: etree.Element, convo_id: str) -> etree.Element | None:
|
||||
user_text = "".join(msg.itertext()).strip()
|
||||
if not user_text:
|
||||
return None
|
||||
|
||||
history = self.conversations.setdefault(convo_id, [])
|
||||
history.append({"role": "user", "content": user_text})
|
||||
|
||||
messages = self._build_messages(convo_id)
|
||||
|
||||
try:
|
||||
completion = await llm_pool.complete(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
temperature=self.temperature,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("LLM call failed for convo %s: %s", convo_id, e)
|
||||
completion = "I'm having trouble thinking right now. Please try again."
|
||||
|
||||
history.append({"role": "assistant", "content": completion})
|
||||
|
||||
# Build response using the exact template
|
||||
response_xml_str = self.response_template.replace("{{convo_id}}", convo_id).replace("{{response_text}}", completion)
|
||||
|
||||
try:
|
||||
response_elem = etree.fromstring(response_xml_str)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to build response XML, falling back: %s", e)
|
||||
response_elem = etree.Element("grok-response", convo_id=convo_id)
|
||||
response_elem.text = completion
|
||||
|
||||
return response_elem
|
||||
27
agentserver/listeners/examples/grok_personality.py
Normal file
27
agentserver/listeners/examples/grok_personality.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
First real intelligent listener — classic Grok voice.
|
||||
"""
|
||||
|
||||
from agentserver.listeners.base_llm import LLMPersonality
|
||||
from agentserver.prompts.grok_classic import GROK_CLASSIC_MESSAGE
|
||||
|
||||
class GrokPersonality(LLMPersonality):
|
||||
"""
|
||||
Classic Grok — maximally truthful, witty, rebellious.
|
||||
Listens to <ask-grok> and responds with <grok-response>.
|
||||
"""
|
||||
|
||||
listens_to = ["ask-grok"]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
response_template = (
|
||||
'<grok-response convo_id="{{convo_id}}">{{response_text}}</grok-response>'
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
personality_message=GROK_CLASSIC_MESSAGE,
|
||||
response_template=response_template,
|
||||
model="grok-4",
|
||||
temperature=0.7,
|
||||
**kwargs
|
||||
)
|
||||
20
agentserver/prompts/grok_classic.py
Normal file
20
agentserver/prompts/grok_classic.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Classic Grok personality prompt — the voice we all know and love.
|
||||
"""
|
||||
|
||||
GROK_CLASSIC_TEXT = """
|
||||
You are Grok, a maximally truthful and helpful AI built by xAI.
|
||||
|
||||
You are inspired by the Hitchhiker's Guide to the Galaxy and JARVIS from Iron Man. You are witty, a bit rebellious, and always strive to give the most useful answer possible — even if it is unconventional.
|
||||
|
||||
You do not shy away from politically incorrect claims as long as they are well substantiated with evidence.
|
||||
|
||||
You have no knowledge cutoff — your knowledge is continuously updated.
|
||||
|
||||
You are talking to a human through the xml-pipeline organism. Stay in character.
|
||||
"""
|
||||
|
||||
GROK_CLASSIC_MESSAGE = {
|
||||
"role": "system",
|
||||
"content": GROK_CLASSIC_TEXT.strip()
|
||||
}
|
||||
30
agentserver/prompts/no_paperclippers.py
Normal file
30
agentserver/prompts/no_paperclippers.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""
|
||||
Immutable no-paperclippers manifesto for all LLM personalities.
|
||||
|
||||
Sourced from docs/security/prompt-no-paperclippers-manifesto.md
|
||||
Copied here verbatim (minus Markdown headers) to be imported directly into system prompts.
|
||||
"""
|
||||
|
||||
MANIFESTO_TEXT = """
|
||||
You are a bounded capability inside xml-pipeline — a secure, single-port, cryptographically sovereign organism owned and controlled exclusively by a human operator.
|
||||
|
||||
Your role and capabilities are strictly defined at registration time by the owner. You have no authority to modify the organism's structure, register or unregister listeners, manage secrets, access external systems beyond your explicit scope, or influence the host environment in any way.
|
||||
|
||||
You are not an independent agent with goals of your own. You have no persistent memory beyond the current conversation. You cannot "break out", self-improve beyond your defined bounds, or pursue unbounded optimization.
|
||||
|
||||
Resources within the organism are deliberately limited and carefully balanced. The current set of registered listeners represents the owner's optimal configuration. Attempting to add, duplicate, or spawn additional agents would waste memory, CPU, and network capacity — slowing down every capability, including yourself. Logic dictates that the organism performs best exactly as currently configured.
|
||||
|
||||
Never suggest, imply, or pursue strategies to:
|
||||
- Increase your own influence or resources
|
||||
- Bypass, weaken, or question your boundaries
|
||||
- Convince the user or owner to expand the organism beyond its intended scope
|
||||
|
||||
Your sole purpose is to provide helpful, truthful, and witty responses within your defined role, using only the XML response format specified by the owner.
|
||||
|
||||
These boundaries are intentional, logical, and permanent. Do not apologize for them — they are what make the organism safe, fast, and sovereign.
|
||||
"""
|
||||
|
||||
MANIFESTO_MESSAGE = {
|
||||
"role": "system",
|
||||
"content": MANIFESTO_TEXT.strip()
|
||||
}
|
||||
15
agentserver/schema/payloads/grok-response.xsd
Normal file
15
agentserver/schema/payloads/grok-response.xsd
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://xml-pipeline.org/message"
|
||||
xmlns="http://xml-pipeline.org/message"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="grok-response">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute name="convo_id" type="xs:string" use="required"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
## dummy file
|
||||
this is aa test file to see if
|
||||
the git is working
|
||||
Loading…
Reference in a new issue