Initial commit: Air Agent Framework v2:全双工对话智能体(打断、插话、主动开口)
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import sys
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
|
||||
from agent.kernel import EventBus, Event, EventType, AgentRuntime
|
||||
from agent.llm import LLMClient
|
||||
from agent.personality import Personality
|
||||
from agent.conversation import ConversationManager
|
||||
from agent.interruption import InterruptionEngine
|
||||
from agent.thinker import Thinker
|
||||
from agent.orchestrator import Orchestrator
|
||||
from agent.tools.registry import ToolRegistry
|
||||
from agent.tools.file_tools import register_file_tools
|
||||
from agent.tools.bash_tools import register_bash_tools
|
||||
from agent.tools.ask import register_ask_tools
|
||||
|
||||
from config.settings import AppSettings
|
||||
|
||||
|
||||
class AgentThread(QThread):
|
||||
message_chunk = pyqtSignal(str)
|
||||
message_done = pyqtSignal(str)
|
||||
agent_thinking = pyqtSignal()
|
||||
agent_idle = pyqtSignal()
|
||||
error_occurred = pyqtSignal(str)
|
||||
interrupt_signal = pyqtSignal(str, str)
|
||||
idle_topic_signal = pyqtSignal(str)
|
||||
|
||||
def __init__(self, config: AppSettings):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
self._loop: asyncio.AbstractEventLoop = None
|
||||
self._running = False
|
||||
self._input_queue: asyncio.Queue[str] = None
|
||||
|
||||
def run(self):
|
||||
self._loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self._loop)
|
||||
self._loop.run_until_complete(self._run_agent())
|
||||
|
||||
async def _run_agent(self):
|
||||
self._input_queue = asyncio.Queue()
|
||||
self._running = True
|
||||
|
||||
runtime = AgentRuntime(self.config)
|
||||
llm = LLMClient(self.config.llm)
|
||||
bus = runtime.bus
|
||||
personality = Personality.from_config({
|
||||
"name": self.config.personality.name,
|
||||
"style": self.config.personality.style,
|
||||
"proactive_chat": self.config.personality.proactive_chat,
|
||||
"idle_timeout": self.config.personality.idle_timeout,
|
||||
"humor_enabled": self.config.personality.humor_enabled,
|
||||
"empathy_enabled": self.config.personality.empathy_enabled,
|
||||
})
|
||||
conversation = ConversationManager()
|
||||
interruption = InterruptionEngine(llm=llm, config=self.config)
|
||||
thinker = Thinker(bus, llm, conversation, self.config)
|
||||
orchestrator = Orchestrator(runtime, llm, personality, conversation, interruption, thinker)
|
||||
|
||||
conversation.create_topic("general")
|
||||
await runtime.start()
|
||||
|
||||
thinker_task = runtime.create_task(thinker.run())
|
||||
|
||||
async def on_thought(event: Event):
|
||||
data = event.data
|
||||
if data.get("type") == "idle_chat":
|
||||
self.idle_topic_signal.emit(data.get("suggestion", ""))
|
||||
|
||||
bus.on(EventType.AGENT_THOUGHT, on_thought)
|
||||
|
||||
try:
|
||||
while self._running:
|
||||
try:
|
||||
user_text = await asyncio.wait_for(
|
||||
self._input_queue.get(), timeout=0.5
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
continue
|
||||
|
||||
thinker.notify_user_activity()
|
||||
conversation.add_message("user", user_text)
|
||||
self.agent_thinking.emit()
|
||||
|
||||
system_prompt = personality.build_system_prompt()
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
messages.extend(conversation.get_context_window(20))
|
||||
|
||||
try:
|
||||
full = ""
|
||||
async for chunk in llm.chat_stream_iter(messages):
|
||||
full += chunk
|
||||
self.message_chunk.emit(chunk)
|
||||
self.message_done.emit(full)
|
||||
if full.strip():
|
||||
conversation.add_message("assistant", full)
|
||||
except Exception as e:
|
||||
self.error_occurred.emit(str(e))
|
||||
|
||||
self.agent_idle.emit()
|
||||
|
||||
finally:
|
||||
await runtime.stop()
|
||||
await llm.close()
|
||||
|
||||
def send_message(self, text: str):
|
||||
if self._input_queue and self._running:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self._input_queue.put(text), self._loop
|
||||
)
|
||||
|
||||
def check_interruption(self, partial_text: str, pause: float):
|
||||
if not self._running or not self._loop:
|
||||
return
|
||||
config = self.config
|
||||
llm = LLMClient(config.llm)
|
||||
engine = InterruptionEngine(llm=llm, config=config)
|
||||
|
||||
async def _check():
|
||||
decision = await engine.evaluate(partial_text, pause, False)
|
||||
if decision.should_interrupt:
|
||||
self.interrupt_signal.emit(
|
||||
decision.suggested_response, decision.style
|
||||
)
|
||||
|
||||
asyncio.run_coroutine_threadsafe(_check(), self._loop)
|
||||
|
||||
def stop_agent(self):
|
||||
self._running = False
|
||||
Reference in New Issue
Block a user