适用场景
当你写的编码智能体(Coding Agent)需要调用对象存储、消息队列这类云服务才能跑通完整流程时,用真实云账号做集成测试会带来三个麻烦:要配密钥、要花流量费、断网或 CI 环境里直接跑不起来。用 Floci 在本机把云服务模拟出来,就能拿到一个稳定的假 S3 和假队列,让智能体在没有云账号、没有外网、没有真实密钥的环境里把工具调用链路完整跑一遍。
环境与前置条件
| 项目 | 要求 |
|---|---|
| 操作系统 | Linux / macOS;Windows 建议用 WSL2 |
| 容器运行时 | Docker 引擎 + Docker Compose 插件 |
| Python | 3.9 及以上(示例用 boto3 调用) |
| 内存 | 建议 8 GB 起步,同时跑容器和本地模型时再往上加 |
| 磁盘 | 预留 10 GB 以上给镜像和模拟数据 |
| 网络 | 首次拉取镜像需要外网,之后可完全离线 |
不需要云账号,不需要真实的 Access Key。示例里的 test / test 是这类本地模拟服务的通用占位凭证,具体是否校验签名以 Floci 官方文档当前版本为准。
分步骤部署
第 1 步:确认容器运行时可用
```bash
docker version
docker compose version
```
这一步只是确认 Docker 客户端能连上守护进程。成功标志是 docker version 同时打印出 Client 和 Server 两段信息——如果只有 Client,说明 Docker 守护进程没启动。
第 2 步:启动 Floci 模拟服务
Floci 以容器方式运行最省事。镜像名称、标签和启动参数以官方文档当前版本为准,下面是通用写法:
```bash
docker run -d \
--name floci \
-p 4566:4566 \
--restart unless-stopped \
<floci-image>
```
如果官方提供了 CLI 启动方式,用官方命令即可,端口保持一致。
说明:-p 4566:4566 把容器端口映射到本机,4566 是本地云模拟服务常见的默认端口,如果你的版本默认端口不同,后面所有 --endpoint-url 都要跟着改。
成功标志:
```bash
docker ps --filter name=floci
```
STATUS 一列显示 Up ... 即为正常。若容器秒退,先看日志:
```bash
docker logs --tail 50 floci
```
第 3 步:准备假凭证与环境变量
```bash
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1
export AWS_ENDPOINT_URL=http://localhost:4566
清掉可能干扰的配置来源
unset AWS_PROFILE
unset AWS_SESSION_TOKEN
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY
```
这步在做什么:AWS SDK 的凭证链会优先读环境变量,再读 ~/.aws/credentials。把 AWS_PROFILE 和 AWS_SESSION_TOKEN 清掉,是为了防止智能体进程意外捞到本机真实凭证,从而连到线上环境。
把这些变量写进项目根目录的 .env 或 docker-compose.yml 里更稳妥,注意别提交包含真实密钥的文件。
第 4 步:用 AWS CLI 打通 S3 链路
```bash
aws s3 mb s3://agent-sandbox --endpoint-url http://localhost:4566
aws s3 ls --endpoint-url http://localhost:4566
echo "hello sandbox" > hello.txt
aws s3 cp hello.txt s3://agent-sandbox/hello.txt --endpoint-url http://localhost:4566
aws s3 ls s3://agent-sandbox/ --endpoint-url http://localhost:4566
```
mb 是 make bucket,ls 列出桶或桶内对象。成功标志是最后一条命令能看到 hello.txt 这一行。能看到它,说明假 S3 已经通了。
新版本 AWS CLI 支持通过 AWS_ENDPOINT_URL 环境变量自动改写端点,此时可以省略 --endpoint-url,以官方文档当前版本的行为为准。
第 5 步:创建假队列
```bash
aws sqs create-queue --queue-name agent-tasks --endpoint-url http://localhost:4566
aws sqs list-queues --endpoint-url http://localhost:4566
```
create-queue 会返回一个 JSON,里面的 QueueUrl 形如:
```
http://localhost:4566/000000000000/agent-tasks
```
把这个 URL 原样复制下来,导出成环境变量:
```bash
export QUEUE_URL=http://localhost:4566/000000000000/agent-tasks
```
这个 URL 里带端口和账号段,不要自己拼字符串,直接取返回值最省事。
第 6 步:写一个 Agent 工具层
智能体真正调用的是函数,所以在 SDK 外面包一层薄薄的工具函数,把端点固定成本地:
```python
tools.py
import os
import json
import boto3
from botocore.config import Config
ENDPOINT = os.environ.get("AWS_ENDPOINT_URL", "http://localhost:4566")
_client_kwargs = dict(
endpoint_url=ENDPOINT,
region_name="us-east-1",
aws_access_key_id="test",
aws_secret_access_key="test",
config=Config(retries={"max_attempts": 3, "mode": "standard"}),
)
s3 = boto3.client("s3", **_client_kwargs)
sqs = boto3.client("sqs", **_client_kwargs)
def put_object(bucket: str, key: str, body: str) -> str:
"""把字符串写进假 S3,返回对象地址。"""
s3.put_object(Bucket=bucket, Key=key, Body=body.encode("utf-8"))
return f"s3://{bucket}/{key}"
def get_object(bucket: str, key: str) -> str:
"""从假 S3 读回字符串。"""
obj = s3.get_object(Bucket=bucket, Key=key)
return obj["Body"].read().decode("utf-8")
def send_task(queue_url: str, payload: dict) -> str:
"""往假队列投递一条 JSON 任务。"""
resp = sqs.send_message(QueueUrl=queue_url, MessageBody=json.dumps(payload))
return resp["MessageId"]
def receive_tasks(queue_url: str, max_messages: int = 10) -> list:
"""从假队列拉取任务,返回带 receipt 的列表。"""
resp = sqs.receive_message(
QueueUrl=queue_url,
MaxNumberOfMessages=max_messages,
WaitTimeSeconds=1,
)
out = []
for msg in resp.get("Messages", []):
out.append({
"id": msg["MessageId"],
"body": json.loads(msg["MessageBody"]),
"receipt": msg["ReceiptHandle"],
})
return out
def delete_task(queue_url: str, receipt: str) -> None:
"""确认任务处理完毕,从队列里删掉。"""
sqs.delete_message(QueueUrl=queue_url, ReceiptHandle=receipt)
TOOLS = {
"put_object": put_object,
"get_object": get_object,
"send_task": send_task,
"receive_tasks": receive_tasks,
"delete_task": delete_task,
}
```
关键点是 endpoint_url 写死在本地地址上。这样无论宿主机的环境变量怎么变,智能体发出的请求都不会跑到真实云端。
接下来把 TOOLS 的键和函数签名转成 function calling 的 JSON Schema 注册给模型,或者在固定流程里直接按名字取函数调用,两种方式都可以。
第 7 步:跑一次端到端工具调用
```python
demo.py
import os
import json
from tools import put_object, get_object, send_task, receive_tasks, delete_task
BUCKET = "agent-sandbox"
QUEUE_URL = os.environ["QUEUE_URL"]
print("1) 写对象:", put_object(
BUCKET, "reports/run-001.json", json.dumps({"status": "ok", "score": 42})
))
print("2) 读对象:", get_object(BUCKET, "reports/run-001.json"))
message_id = send_task(QUEUE_URL, {
"action": "summarize",
"target": f"s3://{BUCKET}/reports/run-001.json",
})
print("3) 投递任务:", message_id)
for task in receive_tasks(QUEUE_URL):
print("4) 取到任务:", json.dumps(task["body"], ensure_ascii=False))
delete_task(QUEUE_URL, task["receipt"])
print("5) 收尾完成")
```
运行:
```bash
QUEUE_URL=http://localhost:4566/000000000000/agent-tasks python demo.py
```
预期输出五行,最后一行是 5) 收尾完成。这五步就是一个最小闭环:写报告 → 读回来 → 投递任务 → 消费任务 → 确认删除。把 put_object / send_task 这类函数注册成工具后,智能体自己就能按需串起这条链路。
第 8 步:断网验证
关掉 Wi-Fi、拔掉网线,或断开 VPN。因为模拟服务和 Agent 都跑在本机,localhost 通信不受影响。再跑一次:
```bash
python demo.py && echo "OFFLINE OK"
```
看到 OFFLINE OK,就说明整条链路确实没有偷偷访问真实云。这一步是整篇教程里价值的一部分——它能证明你的测试是真隔离的。
验证部署是否成功
按顺序核对下面几项,全过就算搭好了:
1. docker ps --filter name=floci 显示状态为 Up。
2. aws s3 ls --endpoint-url http://localhost:4566 能列出 agent-sandbox。
3. aws s3 ls s3://agent-sandbox/ --endpoint-url http://localhost:4566 能看到 hello.txt。
4. aws sqs list-queues --endpoint-url http://localhost:4566 返回的 URL 里包含 agent-tasks。
5. 模拟服务端口可直连:
```bash
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:4566/
```
只要不是 000(连接失败),就说明端口在监听。
6. 断网后 python demo.py 正常退出并打印 OFFLINE OK。
常见报错与解决
报错 1:EndpointConnectionError: Could not connect to the endpoint URL: "http://localhost:4566/"
原因:模拟服务没启动、容器已退出,或者端口没有正确映射到宿主机。
解决:
```bash
docker ps -a --filter name=floci
docker logs --tail 50 floci
docker run -d --name floci -p 4566:4566 <floci-image>
```
报错 2:InvalidAccessKeyId 或 The AWS Access Key Id you provided does not exist in our records.
原因:进程读到的是真实云凭证或空凭证,AWS_PROFILE 指向了别的配置。
解决:
```bash
unset AWS_PROFILE AWS_SESSION_TOKEN AWS_CONFIG_FILE
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1
```
报错 3:SignatureDoesNotMatch
原因:请求被 HTTP 代理拦截后重新签署,或者 AWS_SESSION_TOKEN 残留导致签名头和模拟服务预期不一致。
解决:
```bash
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY
unset AWS_SESSION_TOKEN
```
报错 4:An error occurred (NoSuchBucket) when calling the PutObject operation
原因:桶还没建,或者建桶时用了 A 端点、写入时用了 B 端点。
解决:统一使用同一个端点重新建桶。
```bash
aws s3 mb s3://agent-sandbox --endpoint-url http://localhost:4566
```
报错 5:An error occurred (QueueDoesNotExist)
原因:QueueUrl 是手写的,漏了账号段或端口。
解决:从返回值里原样取。
```bash
aws sqs list-queues --endpoint-url http://localhost:4566
export QUEUE_URL=http://localhost:4566/000000000000/agent-tasks # 以实际返回为准
```
报错 6:容器启动即退出,日志里有 exec format error
原因:镜像架构和本机 CPU 架构不匹配(例如在 Apple Silicon 上跑 amd64 镜像)。
解决:
```bash
docker run -d --platform linux/amd64 --name floci -p 4566:4566 <floci-image>
```
报错 7:bind: address already in use
原因:4566 端口已被别的进程占用。
解决:
```bash
lsof -i :4566
换一个宿主机端口,例如 -p 4567:4566
docker run -d --name floci -p 4567:4566 <floci-image>
```
换端口后,记得同步更新 AWS_ENDPOINT_URL、QueueUrl 和代码里的 endpoint_url。
后续维护
数据持久化与备份。 容器删除后模拟数据一并消失。建议挂载数据卷启动:
```bash
docker run -d --name floci -p 4566:4566 \
-v floci-data:/var/lib/floci \
<floci-image>
```
(容器内数据目录以官方文档说明为准。)除此之外,定期把假 S3 里的对象同步到本地目录做离线备份更直接:
```bash
aws s3 sync s3://agent-sandbox ./backup/agent-sandbox --endpoint-url http://localhost:4566
```
把 backup/ 加进版本控制或对象存储,就是一份可回滚的快照。
升级。 拉新镜像前先导出数据,再删旧容器重建。具体的镜像标签策略以 Floci 官方文档当前版本为准。升级后重点回归两件事:桶和队列能否正常创建、demo.py 是否仍然全绿。
```bash
docker pull <floci-image>
docker rm -f floci
docker run -d --name floci -p 4566:4566 -v floci-data:/var/lib/floci <floci-image>
python demo.py
```
日志与排查。 服务侧看容器日志:
```bash
docker logs -f floci
```
客户端侧开 SDK 调试日志,能看到每条请求打到哪个端点:
```bash
export AWS_DEBUG=1
或在 Python 里
import logging; logging.basicConfig(level=logging.DEBUG)
```
放进 CI 当健康检查。 把第 7 步的 demo.py 当作冒烟测试,在流水线里先起容器、再跑脚本、最后退出码非零就失败。跑完加一段清理,避免用例之间互相污染:
```bash
aws s3 rm s3://agent-sandbox --recursive --endpoint-url http://localhost:4566
aws s3 rb s3://agent-sandbox --endpoint-url http://localhost:4566
aws sqs delete-queue --queue-url "$QUEUE_URL" --endpoint-url http://localhost:4566
```
凭证卫生。 沙箱里只用 test / test 这类占位值。不要把真实 Access Key 写进 .env、docker-compose.yml 或 CI Secrets,否则某天环境变量加载顺序一变,智能体的测试请求就可能打到真实账号上,产生意料之外的账单和数据污染。
资源限制。 在 CI 这类共享机器上给容器加内存和 CPU 上限,避免沙箱挤占其他任务:
```bash
docker run -d --name floci -p 4566:4566 \
--memory 2g --cpus 2 \
<floci-image>
```
把这套沙箱接进智能体之后,开发循环会变得很短:改代码、跑测试、看结果,全程不需要网络也不需要密钥。等逻辑在假 S3 和假队列上稳定了,再切到真实云做一次小规模验证,风险和维护成本都会低不少。
