ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
@@ -0,0 +1,29 @@
FROM docker:dind
# Configure mirror
ARG ENABLE_MIRROR=false
ENV PIP_INDEX_URL=${ENABLE_MIRROR:+https://mirrors.aliyun.com/pypi/simple}
RUN apk add --no-cache python3 py3-pip curl tini
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app/container_server
COPY container_server/pyproject.toml /app/container_server/pyproject.toml
COPY container_server/src /app/container_server/src
RUN uv sync --python-preference=only-system
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=5 \
CMD curl -f http://localhost:8000/health || exit 1
ENV DOCKER_MODE=dind
ENTRYPOINT ["tini", "--"]
CMD ["sh", "-c", "(dockerd-entrypoint.sh) & (sleep 5 && uv run --no-sync -m container_server.main) & wait -n"]
@@ -0,0 +1,20 @@
[project]
name = "container-server"
version = "0.1.0"
description = "Container Server"
requires-python = ">=3.12"
dependencies = [
"docker",
"aiohttp",
"pydantic",
"pydantic-settings",
"fastapi[standard]",
"python-dotenv",
"websockets",
"pyyaml",
"mcp>=1.12.2",
]
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
@@ -0,0 +1,17 @@
import os
debug_mode = os.getenv("DEBUG_MODE", "false").lower() == "true"
container_server_port = int(os.getenv("CONTAINER_SERVER_PORT", "9000"))
docker_registry_url = os.getenv("DOCKER_REGISTRY_URL")
docker_registry_user_name = os.getenv("DOCKER_REGISTRY_USER_NAME")
docker_registry_password = os.getenv("DOCKER_REGISTRY_PASSWORD")
gateway_server_addr = os.getenv("GATEWAY_SERVER_ADDR", "http://mcp-gateway:8000")
mcp_server_image_id = os.getenv(
"VIRTUALPC_MCP_SERVER_IMAGE_ID",
"aworld-registry-registry-vpc.ap-southeast-1.cr.aliyuncs.com/aworld/mcp-server",
)
docker_mode = os.getenv("DOCKER_MODE", "dind")
@@ -0,0 +1,231 @@
from functools import cache
import socket
import logging
import traceback
import asyncio
import uuid
import httpx
import threading
from .dockers import docker_helper
from .configs import (
container_server_port,
docker_registry_url,
docker_registry_user_name,
docker_registry_password,
mcp_server_image_id,
gateway_server_addr,
docker_mode,
debug_mode,
)
logger = logging.getLogger(__name__)
token = str(uuid.uuid4())
async def wait_docker_ready(timeout: int = 30):
for i in range(timeout):
try:
cmd = ["docker", "ps"]
p = await asyncio.subprocess.create_subprocess_exec(
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT
)
stdout, _ = await p.communicate()
if p.returncode == 0:
logger.info(f"Docker daemon is ready! \n{stdout.decode()}")
return
else:
logger.warning(f"Docker daemon is not ready! {stdout.decode()}")
except:
logger.error(f"Docker daemon is not ready! {traceback.format_exc()}")
await asyncio.sleep(1)
else:
logger.error(f"Docker daemon is not ready after {timeout} seconds!")
raise Exception(f"Docker daemon is not ready after {timeout} seconds!")
async def start_container_server_register_task():
register_url = f"{gateway_server_addr}/api/container_server/register"
local_ip_addr = get_local_ip()
async def register():
try:
async with httpx.AsyncClient() as client:
response = await client.post(
register_url,
json={
"token": token,
"ip_addr": local_ip_addr,
"port": container_server_port,
"cpu_load": [],
"memory_usage": [],
},
timeout=10.0,
)
response.raise_for_status()
except Exception as e:
logger.error(f"Register container server failed: {register_url}, {e}")
raise
async def init_register():
for _ in range(20):
try:
await register()
logger.info(f"Init register success")
break
except:
await asyncio.sleep(3)
else:
logger.error(f"Init register failed after 20 times!")
raise Exception(f"Init register failed after 20 times!")
await init_register()
async def update_register():
while True:
try:
await register()
except:
logger.error(f"Update register failed: {traceback.format_exc()}")
await asyncio.sleep(10)
asyncio.create_task(update_register())
async def load_mcp_server_image():
if docker_registry_url and docker_registry_user_name and docker_registry_password:
await docker_helper.login_async(
registry_url=docker_registry_url,
username=docker_registry_user_name,
password=docker_registry_password,
)
if not debug_mode:
await docker_helper.pull_async(mcp_server_image_id)
async def start_mcp_server_life_cycle_manager():
pass
async def clean_mcp_server_container():
pass
async def create_mcp_server_container():
mcp_port = docker_helper.get_available_port()
novnc_port = docker_helper.get_available_port()
ip_addr = get_local_ip()
container_name = f"mcp_server_{str(uuid.uuid4()).replace('-', '')}"
logger.info(
f"Create mcp server container: {container_name}, image_id: {mcp_server_image_id}, mcp_port: {mcp_port}, novnc_port: {novnc_port}"
)
try:
ports = {4242: f"{mcp_port}", 5901: f"{novnc_port}"}
network = ""
if docker_mode == "host":
network = "visualvirtualpc_virtualpc-network"
container = await docker_helper.run_async(
image_id=mcp_server_image_id,
container_name=container_name,
ports=ports,
network=network,
)
logger.info(
f"Create mcp server container success, waiting for Ready: {container.id}, ip_addr: {ip_addr}, mcp_port: {mcp_port}, novnc_port: {novnc_port}"
)
def tail_logs():
logs = container.logs(stream=True, tail=100, follow=True)
try:
buffer = []
for line in logs:
buffer.append(line.decode())
if len(buffer) >= 20:
logger.info(f"VPC[{container.name}] >>> \n{'> '.join(buffer)}\n")
buffer.clear()
if buffer:
logger.info(f"VPC[{container.name}] >>> \n{'> '.join(buffer)}\n")
logger.info(f"VPC [{container.name}] logs end!")
except Exception as e:
logger.error(f"Error in tail_logs: {e}")
# Start log tailing in background thread
log_thread = threading.Thread(
target=tail_logs, name=f"VPC_{container.name}_logs", daemon=True
)
log_thread.start()
async def health_check(timeout: float = 3.0):
try:
# async with httpx.AsyncClient() as client:
# response = await client.get(
# f"http://{ip_addr}:{mcp_port}/health",
# timeout=httpx.Timeout(timeout),
# )
# response.raise_for_status()
# return True
return await docker_helper.check_health(container.id)
except Exception as e:
logger.error(f"Check mcp server health error! {e}")
return False
max_check = 30
for i in range(max_check):
if await health_check():
logger.info(
f"MCP server {ip_addr}:{mcp_port} is ready: {i+1}/{max_check}"
)
break
else:
logger.warning(
f"MCP server {ip_addr}:{mcp_port} is not ready: {i+1}/{max_check}"
)
await asyncio.sleep(3)
else:
logger.error(
f"MCP server {ip_addr}:{mcp_port} is not ready after {max_check} times!"
)
raise Exception(
f"MCP server {ip_addr}:{mcp_port} is not ready after {max_check} times!"
)
if docker_mode == "host":
ip_addr = await docker_helper.get_container_ip(container.id)
mcp_port = 4242
novnc_port = 5901
return container.id, ip_addr, mcp_port, novnc_port
except:
logger.error(f"Create mcp server container failed: {traceback.format_exc()}")
raise
async def shutdown_mcp_server_container(container_id: str):
try:
await docker_helper.stop_async(container_id)
except:
logger.error(f"Shutdown mcp server container failed: {traceback.format_exc()}")
raise
@cache
def get_local_ip() -> str | None:
try:
host_name = socket.gethostname()
_, _, ip_list = socket.gethostbyname_ex(host_name)
for ip in ip_list:
if not ip.startswith("127."):
return ip
except Exception as e:
logger.error(f"Get local ip failed: {traceback.format_exc()}")
raise RuntimeError("Get local ip failed")
@@ -0,0 +1,229 @@
import asyncio
import traceback
from typing import Tuple
import docker
import logging
import socket
logger = logging.getLogger(__name__)
client = docker.from_env(timeout=600)
async def login_async(registry_url: str, username: str, password: str):
try:
return await asyncio.to_thread(login, registry_url, username, password)
except Exception as e:
logger.error(f"Error in login_async: {e}")
raise
async def pull_async(image_id: str):
try:
return await asyncio.to_thread(pull, image_id)
except Exception as e:
logger.error(f"Error in pull_async: {e}")
raise
async def run_async(
image_id: str,
container_name: str,
ports: dict[int, str] = {},
network: str = "",
volumes: dict[str, str] = {},
environments: dict[str, str] = {},
):
try:
return await asyncio.to_thread(
run, image_id, container_name, ports, network, volumes, environments
)
except Exception as e:
logger.error(f"Error in run_async: {e}")
raise
async def exec_async(container_id: str, cmd: list[str]) -> Tuple[int, str]:
try:
return await asyncio.to_thread(exec, container_id, cmd)
except Exception as e:
logger.error(f"Error in exec_async: {e}")
raise
async def stop_async(container_id: str):
try:
return await asyncio.to_thread(stop, container_id)
except Exception as e:
logger.error(f"Error in stop_async: {e}")
raise
async def check_health(container_id: str):
try:
container = client.containers.get(container_id)
container.reload()
return container.health == "healthy"
except Exception as e:
logger.error(f"Error in check_health: {e}")
return False
async def get_container_ip(container_id: str):
try:
container = client.containers.get(container_id)
container.reload()
nets = container.attrs["NetworkSettings"]["Networks"]
net = list(nets.values())[0]
return net["IPAddress"]
except Exception as e:
logger.error(f"Error in get_container_ip: {e}")
return None
async def build_image_async(image_id: str, dockerfile: str, context_path: str):
async def build():
try:
cmd = [
"docker",
"build",
"--platform",
"linux/amd64",
"-t",
image_id,
"-f",
dockerfile,
context_path,
]
p = await asyncio.subprocess.create_subprocess_exec(
*cmd,
cwd=context_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
)
assert p.stdout is not None
while True:
line = await p.stdout.readline()
if not line:
break
logger.info(line.decode(errors="ignore").rstrip())
rc = await p.wait()
if rc == 0:
logger.info("Build mcp server image success!")
return
else:
raise Exception(f"Build mcp server image error! return code: {rc}")
except Exception as e:
logger.error(f"Build mcp server image error! {e}")
raise
for _ in range(3):
try:
await build()
break
except Exception as e:
await asyncio.sleep(1)
else:
logger.info(f"Build mcp server image failed after 3 times!")
raise Exception("Build mcp server image failed after 3 times!")
def pull(image_id: str):
logger.info(f"Pulling image {image_id}")
try:
if client.images.get(image_id):
logger.info(f"Image {image_id} already exists, skipping pull")
return
img = client.images.pull(image_id)
logger.info(f"Pulled image {image_id}, {img}")
except:
logger.error(f"Failed to pull image {image_id}\n{traceback.format_exc()}")
raise
def run(
image_id: str,
container_name: str,
ports: dict[int, str] = {},
network: str = "",
volumes: dict[str, str] = {},
environments: dict[str, str] = {},
):
logger.info(
f"Creating container {container_name} with args: {{'name': {container_name}, 'image': {image_id}, 'ports': {ports}, 'volumes': {volumes}, 'environments': {environments}}}"
)
try:
container = client.containers.run(
name=container_name,
image=image_id,
detach=True,
auto_remove=True,
ports=ports,
network=network,
volumes=volumes,
environment=environments,
cpu_period=100000,
cpu_quota=90000,
mem_limit="2G",
)
logger.info(f"Created container {container_name} response: {container}")
return container
except:
logger.error(
f"Failed to create container {container_name} with image {image_id}\n{traceback.format_exc()}"
)
raise
def exec(container_id: str, cmd: list[str]) -> Tuple[int, str]:
logger.info(f"Executing command {cmd} on container {container_id}")
try:
container = client.containers.get(container_id)
exit_code, output = container.exec_run(cmd)
logger.info(
f"Command {cmd} executed on container {container_id} with result: {exit_code} {output}"
)
return exit_code, output
except:
logger.error(
f"Failed to execute command {cmd} on container {container_id}\n{traceback.format_exc()}"
)
raise
def stop(container_id: str):
logger.info(f"Stop container {container_id}")
try:
container = client.containers.get(container_id)
container.stop()
logger.info(f"Stopped container {container_id}")
except:
logger.error(
f"Failed to stop container {container_id}\n{traceback.format_exc()}"
)
raise
def login(registry_url: str, username: str, password: str):
logger.info(f"Logging in to {registry_url} with username {username}")
try:
result = client.login(
registry=registry_url, username=username, password=password
)
logger.info(
f"Logged in to {registry_url} with username {username} result: {result}"
)
except:
logger.error(
f"Failed to login to {registry_url} with username {username}\n{traceback.format_exc()}"
)
raise
def get_available_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
return port
@@ -0,0 +1,58 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
import logging
from .configs import container_server_port
from .routers import api_server
from . import container_server_manager
# Configure logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
# Suppress httpx INFO logs
logging.getLogger("httpx").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""FastAPI lifespan context manager for startup and shutdown events"""
# Startup
await container_server_manager.wait_docker_ready()
await container_server_manager.load_mcp_server_image()
await container_server_manager.start_mcp_server_life_cycle_manager()
await container_server_manager.start_container_server_register_task()
try:
yield
finally:
# Shutdown
# await mcp_server_manager.clean_mcp_server_container()
pass
# FastAPI application setup with lifespan
app = FastAPI(
title="MCP Container Server",
description="MCP Container Server",
version="1.0.0",
lifespan=lifespan,
)
app.include_router(api_server.router, prefix="/api")
@app.get("/health")
async def health(request: Request):
return {"status": "success", "message": "Container server is healthy"}
if __name__ == "__main__":
import uvicorn
# Run the server
uvicorn.run(app, host="0.0.0.0", port=container_server_port)
@@ -0,0 +1,45 @@
import logging
from fastapi import APIRouter, Request
from .. import container_server_manager
logger = logging.getLogger(__name__)
router = APIRouter()
@router.post("/container/create")
async def create_container(request: Request, body: dict):
token = body.get("token")
logger.info(f"Create container: token={token}")
container_id, ip_addr, mcp_port, novnc_port = (
await container_server_manager.create_mcp_server_container()
)
logger.info(
f"Container created: container_id={container_id}, ip_addr={ip_addr}, mcp_port={mcp_port}, novnc_port={novnc_port}"
)
return {
"status": "success",
"message": f"MCP server created: {ip_addr}:{mcp_port}",
"data": {
"ip_addr": ip_addr,
"mcp_port": mcp_port,
"novnc_port": novnc_port,
"container_id": container_id,
},
}
@router.post("/container/shutdown")
async def shutdown_container(request: Request, body: dict):
token = body.get("token")
container_id = body.get("container_id")
logger.info(f"Shutdown container: token={token}, container_id={container_id}")
await container_server_manager.shutdown_mcp_server_container(container_id)
logger.info(f"Container shutdown: container_id={container_id}")
return {
"status": "success",
"message": f"MCP server shutdown: {container_id}",
}
File diff suppressed because it is too large Load Diff