Initial commit: Air Agent Framework v2:全双工对话智能体(打断、插话、主动开口)
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import time
|
||||
import uuid
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Awaitable
|
||||
|
||||
|
||||
class EventType(Enum):
|
||||
USER_MESSAGE = "user_message"
|
||||
USER_INPUT_CHANGE = "user_input_change"
|
||||
USER_PAUSE = "user_pause"
|
||||
USER_RESUME = "user_resume"
|
||||
AGENT_MESSAGE = "agent_message"
|
||||
AGENT_THOUGHT = "agent_thought"
|
||||
AGENT_STREAM_CHUNK = "agent_stream_chunk"
|
||||
INTERRUPT = "interrupt"
|
||||
QUESTION = "question"
|
||||
QUESTION_ANSWER = "question_answer"
|
||||
TOOL_CALL = "tool_call"
|
||||
TOOL_RESULT = "tool_result"
|
||||
STATE_CHANGE = "state_change"
|
||||
IDLE = "idle"
|
||||
ERROR = "error"
|
||||
SHUTDOWN = "shutdown"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
type: EventType
|
||||
data: Any = None
|
||||
source: str = ""
|
||||
id: str = field(default_factory=lambda: uuid.uuid4().hex[:8])
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
|
||||
def __repr__(self):
|
||||
return f"[{self.type.value}] {self.data}"
|
||||
|
||||
|
||||
EventHandler = Callable[[Event], Awaitable[None]]
|
||||
|
||||
|
||||
class EventBus:
|
||||
def __init__(self):
|
||||
self._listeners: dict[EventType, list[EventHandler]] = {}
|
||||
self._history: list[Event] = []
|
||||
self._max_history = 1000
|
||||
|
||||
def on(self, event_type: EventType, handler: EventHandler):
|
||||
if event_type not in self._listeners:
|
||||
self._listeners[event_type] = []
|
||||
self._listeners[event_type].append(handler)
|
||||
|
||||
def off(self, event_type: EventType, handler: EventHandler):
|
||||
if event_type in self._listeners:
|
||||
self._listeners[event_type].remove(handler)
|
||||
|
||||
async def emit(self, event: Event):
|
||||
self._history.append(event)
|
||||
if len(self._history) > self._max_history:
|
||||
self._history.pop(0)
|
||||
handlers = self._listeners.get(event.type, [])
|
||||
results = []
|
||||
for handler in handlers:
|
||||
try:
|
||||
results.append(handler(event))
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if results:
|
||||
await asyncio.gather(*results)
|
||||
|
||||
def get_history(self, limit: int = 50) -> list[Event]:
|
||||
return self._history[-limit:]
|
||||
|
||||
|
||||
class AgentRuntime:
|
||||
def __init__(self, config: Any = None):
|
||||
self.bus = EventBus()
|
||||
self.config = config
|
||||
self._running = False
|
||||
self._tasks: set[asyncio.Task] = set()
|
||||
self._shutdown_event = asyncio.Event()
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
return self._running
|
||||
|
||||
def create_task(self, coro) -> asyncio.Task:
|
||||
task = asyncio.create_task(coro)
|
||||
self._tasks.add(task)
|
||||
task.add_done_callback(self._tasks.discard)
|
||||
return task
|
||||
|
||||
async def start(self):
|
||||
self._running = True
|
||||
self._shutdown_event.clear()
|
||||
|
||||
async def stop(self):
|
||||
self._running = False
|
||||
await self.bus.emit(Event(EventType.SHUTDOWN))
|
||||
self._shutdown_event.set()
|
||||
if self._tasks:
|
||||
await asyncio.gather(*self._tasks, return_exceptions=True)
|
||||
|
||||
async def wait_for_shutdown(self):
|
||||
await self._shutdown_event.wait()
|
||||
Reference in New Issue
Block a user