Initial commit: Air Agent Framework v2:全双工对话智能体(打断、插话、主动开口)

This commit is contained in:
WpyQwq
2026-09-19 12:09:51 +08:00
commit fa0cb5824d
26 changed files with 2362 additions and 0 deletions
View File
+22
View File
@@ -0,0 +1,22 @@
from __future__ import annotations
from .registry import ToolRegistry
def register_ask_tools(registry: ToolRegistry):
@registry.tool(
name="ask_user",
description="向用户提问,等待用户回答。用于需要澄清或获取信息时。",
parameters={
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "要问用户的问题",
},
},
"required": ["question"],
},
)
async def ask_user(question: str) -> str:
return f"[等待用户回答: {question}]"
+51
View File
@@ -0,0 +1,51 @@
from __future__ import annotations
import asyncio
from .registry import ToolRegistry
def register_bash_tools(registry: ToolRegistry):
@registry.tool(
name="run_command",
description="执行 shell 命令并返回输出",
parameters={
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "要执行的命令",
},
"timeout": {
"type": "integer",
"description": "超时秒数",
"default": 30,
},
},
"required": ["command"],
},
)
async def run_command(command: str, timeout: int = 30) -> str:
try:
proc = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
try:
stdout, stderr = await asyncio.wait_for(
proc.communicate(), timeout=timeout
)
except asyncio.TimeoutError:
proc.kill()
return f"[命令超时 ({timeout}s)]"
output = ""
if stdout:
output += stdout.decode("utf-8", errors="replace")
if stderr:
output += "\n[STDERR]\n" + stderr.decode("utf-8", errors="replace")
if len(output) > 5000:
output = output[:5000] + "\n\n[输出过长,已截断]"
return output or "[无输出]"
except Exception as e:
return f"[命令执行失败: {e}]"
+73
View File
@@ -0,0 +1,73 @@
from __future__ import annotations
import os
from .registry import ToolRegistry
def register_file_tools(registry: ToolRegistry):
@registry.tool(
name="read_file",
description="读取文件内容",
parameters={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "文件路径",
},
},
"required": ["path"],
},
)
async def read_file(path: str) -> str:
if not os.path.exists(path):
return f"[文件不存在: {path}]"
with open(path, "r", encoding="utf-8", errors="replace") as f:
content = f.read()
if len(content) > 10000:
content = content[:10000] + "\n\n[内容过长,已截断]"
return content
@registry.tool(
name="write_file",
description="写入文件",
parameters={
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"},
"content": {"type": "string", "description": "文件内容"},
},
"required": ["path", "content"],
},
)
async def write_file(path: str, content: str) -> str:
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"[已写入 {len(content)} 字节到 {path}]"
@registry.tool(
name="list_files",
description="列出目录内容",
parameters={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "目录路径",
},
},
"required": ["path"],
},
)
async def list_files(path: str = ".") -> str:
if not os.path.exists(path):
return f"[目录不存在: {path}]"
items = os.listdir(path)
lines = []
for item in sorted(items):
full = os.path.join(path, item)
suffix = "/" if os.path.isdir(full) else ""
lines.append(f" {item}{suffix}")
return f"{path} ({len(lines)} 项):\n" + "\n".join(lines)
+46
View File
@@ -0,0 +1,46 @@
from __future__ import annotations
from typing import Any, Callable, Awaitable, Optional
from ..llm import ToolDef
ToolHandler = Callable[..., Awaitable[str]]
class ToolRegistry:
def __init__(self):
self._tools: dict[str, tuple[ToolDef, ToolHandler]] = {}
def register(
self,
name: str,
description: str,
parameters: dict,
handler: ToolHandler,
):
self._tools[name] = (
ToolDef(name=name, description=description, parameters=parameters),
handler,
)
def get_defs(self) -> list[ToolDef]:
return [t[0] for t in self._tools.values()]
def get_handler(self, name: str) -> Optional[ToolHandler]:
entry = self._tools.get(name)
return entry[1] if entry else None
async def execute(self, name: str, **kwargs) -> str:
handler = self.get_handler(name)
if not handler:
return f"[错误: 工具 '{name}' 不存在]"
try:
return await handler(**kwargs)
except Exception as e:
return f"[工具 '{name}' 执行失败: {e}]"
def tool(self, name: str, description: str, parameters: dict):
def decorator(func: ToolHandler):
self.register(name, description, parameters, func)
return func
return decorator