feat: LLM agent #12
@@ -30,7 +30,7 @@ class BDICoreAgent(BDIAgent):
|
|||||||
self.add_behaviour(BeliefSetter())
|
self.add_behaviour(BeliefSetter())
|
||||||
self.add_behaviour(ReceiveLLMResponseBehaviour())
|
self.add_behaviour(ReceiveLLMResponseBehaviour())
|
||||||
|
|
||||||
await self._send_to_llm("Hello we are the Pepper plus team")
|
await self._send_to_llm("Hi pepper, how are you?")
|
||||||
# This is the example message currently sent to the llm at the start of the Program
|
# This is the example message currently sent to the llm at the start of the Program
|
||||||
|
|
||||||
self.logger.info("BDICoreAgent setup complete")
|
self.logger.info("BDICoreAgent setup complete")
|
||||||
@@ -52,7 +52,7 @@ class BDICoreAgent(BDIAgent):
|
|||||||
self._send_to_llm(message_text)
|
self._send_to_llm(message_text)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
async def _send_to_llm(self, text: str) -> str:
|
async def _send_to_llm(self, text: str):
|
||||||
"""
|
"""
|
||||||
Sends a text query to the LLM Agent asynchronously.
|
Sends a text query to the LLM Agent asynchronously.
|
||||||
"""
|
"""
|
||||||
@@ -61,12 +61,10 @@ class BDICoreAgent(BDIAgent):
|
|||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
msg = Message(
|
msg = Message(
|
||||||
to= settings.agent_settings.llm_agent_name + '@' + settings.agent_settings.host,
|
to= settings.agent_settings.llm_agent_name + '@' + settings.agent_settings.host,
|
||||||
body= text,
|
body= text
|
||||||
thread= "llm_request",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.send(msg)
|
await self.send(msg)
|
||||||
self.agent.logger.debug("Message sent to LLM: %s", text)
|
self.agent.logger.debug("Message sent to LLM: %s", text)
|
||||||
|
|
||||||
self.add_behaviour(SendBehaviour())
|
self.add_behaviour(SendBehaviour())
|
||||||
return "LLM message dispatch scheduled"
|
|
||||||
@@ -15,6 +15,7 @@ from spade.message import Message
|
|||||||
from spade.template import Template
|
from spade.template import Template
|
||||||
|
|
||||||
from control_backend.core.config import settings
|
from control_backend.core.config import settings
|
||||||
|
from control_backend.agents.llm.llm_instructions import LLMInstructions
|
||||||
|
|
||||||
|
|
||||||
class LLMAgent(Agent):
|
class LLMAgent(Agent):
|
||||||
@@ -32,7 +33,7 @@ class LLMAgent(Agent):
|
|||||||
the BDI Core Agent and handle them.
|
the BDI Core Agent and handle them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self):
|
||||||
"""
|
"""
|
||||||
Receives SPADE messages and processes only those originating from the
|
Receives SPADE messages and processes only those originating from the
|
||||||
configured BDI agent.
|
configured BDI agent.
|
||||||
@@ -54,7 +55,7 @@ class LLMAgent(Agent):
|
|||||||
else:
|
else:
|
||||||
self.agent.logger.debug("Message ignored (not from BDI Core Agent)")
|
self.agent.logger.debug("Message ignored (not from BDI Core Agent)")
|
||||||
|
|
||||||
async def _process_bdi_message(self, message: Message) -> None:
|
async def _process_bdi_message(self, message: Message):
|
||||||
"""
|
"""
|
||||||
Forwards user text to the LLM and replies with the generated text.
|
Forwards user text to the LLM and replies with the generated text.
|
||||||
"""
|
"""
|
||||||
@@ -62,14 +63,13 @@ class LLMAgent(Agent):
|
|||||||
llm_response = await self._query_llm(user_text)
|
llm_response = await self._query_llm(user_text)
|
||||||
await self._reply(llm_response)
|
await self._reply(llm_response)
|
||||||
|
|
||||||
async def _reply(self, msg: str) -> None:
|
async def _reply(self, msg: str):
|
||||||
"""
|
"""
|
||||||
Sends a response message back to the BDI Core Agent.
|
Sends a response message back to the BDI Core Agent.
|
||||||
"""
|
"""
|
||||||
reply = Message(
|
reply = Message(
|
||||||
to= settings.agent_settings.bdi_core_agent_name + '@' + settings.agent_settings.host,
|
to= settings.agent_settings.bdi_core_agent_name + '@' + settings.agent_settings.host,
|
||||||
body= msg,
|
body= msg
|
||||||
thread= "llm_response",
|
|
||||||
)
|
)
|
||||||
await self.send(reply)
|
await self.send(reply)
|
||||||
self.agent.logger.info("Reply sent to BDI Core Agent")
|
self.agent.logger.info("Reply sent to BDI Core Agent")
|
||||||
@@ -82,15 +82,30 @@ class LLMAgent(Agent):
|
|||||||
:return: LLM-generated content or fallback message.
|
:return: LLM-generated content or fallback message.
|
||||||
"""
|
"""
|
||||||
async with httpx.AsyncClient(timeout=120.0) as client:
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||||
|
# Example dynamic content for future (optional)
|
||||||
|
|
||||||
|
instructions = LLMInstructions()
|
||||||
|
developer_instruction = instructions.build_developer_instruction()
|
||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
settings.llm_settings.local_llm_url,
|
settings.llm_settings.local_llm_url,
|
||||||
headers={"Content-Type": "application/json"},
|
headers={"Content-Type": "application/json"},
|
||||||
json={
|
json={
|
||||||
"model": settings.llm_settings.local_llm_model,
|
"model": settings.llm_settings.local_llm_model,
|
||||||
"messages": [{"role": "user", "content": prompt}],
|
"messages": [
|
||||||
"temperature": 0.3,
|
{
|
||||||
|
"role": "developer",
|
||||||
|
"content": developer_instruction
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": prompt
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"temperature": 0.3
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data: dict[str, Any] = response.json()
|
data: dict[str, Any] = response.json()
|
||||||
@@ -104,15 +119,12 @@ class LLMAgent(Agent):
|
|||||||
self.agent.logger.error("Unexpected error: %s", err)
|
self.agent.logger.error("Unexpected error: %s", err)
|
||||||
return "Error processing the request."
|
return "Error processing the request."
|
||||||
|
|
||||||
async def setup(self) -> None:
|
async def setup(self):
|
||||||
"""
|
"""
|
||||||
Sets up the SPADE behaviour to filter and process messages from the
|
Sets up the SPADE behaviour to filter and process messages from the
|
||||||
BDI Core Agent.
|
BDI Core Agent.
|
||||||
"""
|
"""
|
||||||
self.logger.info("LLMAgent setup complete")
|
self.logger.info("LLMAgent setup complete")
|
||||||
|
|
||||||
template = Template()
|
|
||||||
template.sender = settings.agent_settings.bdi_core_agent_name + '@' + settings.agent_settings.host
|
|
||||||
|
|
||||||
behaviour = self.ReceiveMessageBehaviour()
|
behaviour = self.ReceiveMessageBehaviour()
|
||||||
self.add_behaviour(behaviour, template)
|
self.add_behaviour(behaviour)
|
||||||
|
|||||||
44
src/control_backend/agents/llm/llm_instructions.py
Normal file
44
src/control_backend/agents/llm/llm_instructions.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
class LLMInstructions:
|
||||||
|
"""
|
||||||
|
Defines structured instructions that are sent along with each request
|
||||||
|
to the LLM to guide its behavior (norms, goals, etc.).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_norms() -> str:
|
||||||
|
return f"""
|
||||||
|
Be friendly and respectful.
|
||||||
|
Make the conversation feel natural and engaging.
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_goals() -> str:
|
||||||
|
return f"""
|
||||||
|
Try to learn the user's name during conversation.
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
def __init__(self, norms: str | None = None, goals: str | None = None):
|
||||||
|
self.norms = norms if norms is not None else self.default_norms()
|
||||||
|
self.goals = goals if goals is not None else self.default_goals()
|
||||||
|
|
||||||
|
def build_developer_instruction(self) -> str:
|
||||||
|
"""
|
||||||
|
Builds a multi-line formatted instruction string for the LLM.
|
||||||
|
Includes only non-empty structured fields.
|
||||||
|
"""
|
||||||
|
sections = [
|
||||||
|
"You are a Pepper robot engaging in natural human conversation.",
|
||||||
|
"Keep responses between 1–5 sentences, unless instructed otherwise.\n",
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.norms:
|
||||||
|
sections.append("Norms to follow:")
|
||||||
|
sections.append(self.norms)
|
||||||
|
sections.append("")
|
||||||
|
|
||||||
|
if self.goals:
|
||||||
|
sections.append("Goals to reach:")
|
||||||
|
sections.append(self.goals)
|
||||||
|
sections.append("")
|
||||||
|
|
||||||
|
return "\n".join(sections).strip()
|
||||||
@@ -44,10 +44,10 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
|
|
||||||
llm_agent = LLMAgent(settings.agent_settings.llm_agent_name + '@' + settings.agent_settings.host,
|
llm_agent = LLMAgent(settings.agent_settings.llm_agent_name + '@' + settings.agent_settings.host,
|
||||||
"secret, ask twirre")
|
settings.agent_settings.llm_agent_name)
|
||||||
await llm_agent.start()
|
await llm_agent.start()
|
||||||
bdi_core = BDICoreAgent(settings.agent_settings.bdi_core_agent_name + '@' + settings.agent_settings.host,
|
bdi_core = BDICoreAgent(settings.agent_settings.bdi_core_agent_name + '@' + settings.agent_settings.host,
|
||||||
"secret, ask twirre", "src/control_backend/agents/bdi/rules.asl")
|
settings.agent_settings.bdi_core_agent_name, "src/control_backend/agents/bdi/rules.asl")
|
||||||
await bdi_core.start()
|
await bdi_core.start()
|
||||||
|
|
||||||
_temp_vad_agent = VADAgent("tcp://localhost:5558", False)
|
_temp_vad_agent = VADAgent("tcp://localhost:5558", False)
|
||||||
|
|||||||
Reference in New Issue
Block a user