1.让我们快速的开始使用iflow cli sdk进行一个小型Agent的搭建吧。

前置需求

Python -sdk
1.昨天在用的时候发现了一件事情


通常情况下 Vscode的面板在下方,可以通过点击右键

面板位置切换到右侧

终端下的copilot,哈哈哈
解决vscode 插件 Windows用户无法连接的问题了吧,还可以感应路径(噗呲)
好啦,那我们开始我们的第一节课

1.一个Agent 那么它是相当于一个掌握了工具的LLM,哈哈哈,大家都很喜欢说叫做能够感知环境。对的,上述说的都很对

但其实呢,LLM仍然是一个只能对话和获取上下文的小能手,只是它多了来自于其它工具给他提供了上下文

简单点来说呢

就拿我们的数据流来看吧
以下是一个真实的Agent循环迭代示意。事实上这是一个宏观图

Agent循环迭代示意

┌────────────────────────────────────────────┐
│                 循环迭代                    │
├────────────────────────────────────────────┤
│                                            │
│   ┌──────┐    ┌──────┐    ┌──────┐      │
│   │ 感知  │───▶│ 思考  │───▶│ 规划  │      │
│   └──────┘    └──────┘    └──────┘      │
│        ▲                              │    │
│        │                              ▼    │
│   ┌──────┐    ┌──────┐    ┌──────┐      │
│   │ 返回  │◀───│ 观察  │◀───│ 执行  │      │
│   └──────┘    └──────┘    └──────┘      │
│                                            │
└────────────────────────────────────────────┘
         ↓ 目标达成或达到最大迭代次数
      结束

哈哈哈,相信这个宏观图大家都知道
看环境——结合看到的内容思考——开始规划——规划后的任务进行执行——下一步进行观察——返回——继续感知或者选择结束感知(哈哈哈,迭代到最后结束嘛)这是一个宏观的迭代

那么作为一个从组件上来看,或者说从LLM + 工具 + 记忆管理 + 规划 + 执行

那么就可以形成一个简单的小Agent了;

事实上大家可以思考一个问题哈,为啥Agent叫Agent啊,它不是一个工作流嘛?不也是工具调用吗。和程序不一样吗?

哈哈哈,其实这里面有关子。

那么我们看看如果用iflow cli sdk 做一个最简单的agent;
以下就是完整的架构图,当然可以进行扩充嘛

完整架构图

┌─────────────────────────────────────────┐
│              Agent                      │
├─────────────────────────────────────────┤
│  ┌─────────┐    ┌─────────┐            │
│  │   LLM   │───▶│ 规划器  │            │
│  └─────────┘    └────┬────┘            │
│                      │                  │
│  ┌─────────┐    ┌────▼────┐            │
│  │ 工具组件 │◀───│ 执行器  │            │
│  └─────────┘    └─────────┘            │
│                      │                  │
│              ┌───────▼───────┐          │
│              │   记忆系统    │          │
│              └───────────────┘          │
└─────────────────────────────────────────┘

那么Agent是个什么玩意呢?

事实上,整个过程中在调控,在分发,在捕捉记忆内容(短长期内容),获取执行结果的都只有一个 你的LLM,哈哈哈,什么脏活累活都给他。

为了让这个Agent最简单 ,不如直接用iflow cli sdk,嘿嘿。这个有python sdk呀,噗呲,是我用的最多的


六大核心组件

来,第一个就是重头戏,那就是

### 1. LLM模块 (`llm.py`)
"""LLM模块 - 与iflow SDK集成"""

import asyncio
from iflow_sdk import IFlowClient, IFlowOptions, AssistantMessage, TaskFinishMessage


class LLM:
    """简化版LLM调用器"""
    
    def __init__(self, url: str = "ws://localhost:8090/acp", timeout: float = 120.0):
        # 初始化LLM连接参数: WebSocket地址和超时时间
        self.url = url
        self.timeout = timeout
    
    async def send(self, message: str) -> str:
        """发送消息并获取响应"""
        # 配置IFlow客户端选项
        options = IFlowOptions(
            url=self.url,
            auto_start_process=True,  # 自动启动流程
            timeout=self.timeout      # 设置超时时间
        )
        
        response_text = ""
        # 创建IFlow客户端连接
        async with IFlowClient(options) as client:
            # 发送用户消息到LLM
            await client.send_message(message)
            
            # 异步接收并处理LLM返回的消息流
            async for msg in client.receive_messages():
                if isinstance(msg, AssistantMessage):
                    # 累积助理的文本响应
                    response_text += msg.chunk.text or ""
                elif isinstance(msg, TaskFinishMessage):
                    # 收到任务完成消息时结束接收
                    break
        
        return response_text
    
    def send_sync(self, message: str) -> str:
        """同步发送"""
        # 包装异步方法为同步调用
        return asyncio.run(self.send(message))


if __name__ == "__main__":
    # 测试代码: 创建LLM实例并测试连接
    llm = LLM()
    print("测试LLM连接...")
    response = llm.send_sync("你好,请简单介绍自己")
    print(f"响应: {response}")
你需要准备一下导入依赖库
随后胡就是LLM调用器;从iflow cli client调用你的LLM
Websocket地址和超时时间

随后用self. 进行配置url 以及超时时间

IFLow CLI Option选择配置客户端

哈哈哈,当然不止代码中的它们了;
还有:
IFlowOptions 参数
IFlowOptions 类用于配置 iFlow 客户端的行为:

核心连接参数
参数	类型	默认值	描述
url	str	"ws://localhost:8090/acp"	WebSocket 连接 URL
auto_start_process	bool	True	是否自动启动 iFlow 进程。设为 False 时需手动启动 iFlow
process_start_port	int	8090	自动启动 iFlow 进程时使用的起始端口号
timeout	float	30.0	连接和操作超时时间(秒)
log_level	str	"INFO"	日志级别,可选值: "DEBUG", "INFO", "WARNING", "ERROR"
cwd	str	当前工作目录	CLI 操作的工作目录
session_id	Optional[str]	None	会话 ID,用于恢复现有会话
认证参数
参数	类型	默认值	描述
auth_method_id	Optional[str]	None	认证方法 ID (例如: "iflow","openai-compatible")
auth_method_info	Optional[Union[Dict, AuthMethodInfo]]	None	认证方法信息 (包含 api_key, base_url, model_name 等)
工具执行控制参数
参数	类型	默认值	描述
approval_mode	ApprovalMode	ApprovalMode.YOLO	工具执行权限模式: DEFAULT(需要确认), AUTO_EDIT(自动执行), YOLO(自动执行+自动回退), PLAN(只读模式)
auto_approve_types	List[str]	["edit", "fetch"]	自动批准的工具类型列表(旧版本,不建议使用)
文件系统访问参数
参数	类型	默认值	描述
file_access	bool	False	是否启用文件系统访问(出于安全考虑默认禁用)
file_allowed_dirs	Optional[List[str]]	None	允许 iFlow 访问的目录列表。None 表示仅当前目录
file_read_only	bool	False	是否仅允许读取操作(启用时禁止写入)
file_max_size	int	10485760	读取操作允许的最大文件大小(字节),默认 10MB
高级配置参数
参数	类型	默认值	描述
mcp_servers	List[Union[Dict, McpServer]]	[]	MCP 服务器配置列表
hooks	Optional[Dict[HookEventType, List[HookEventConfig]]]	None	各种生命周期事件的钩子配置
commands	Optional[List[CommandConfig]]	None	命令配置列表
agents	Optional[List[CreateAgentConfig]]	None	代理配置列表
session_settings	Optional[SessionSettings]	None	会话特定设置(包括 allowed_tools, disallowed_tools, system_prompt 等)
metadata	Dict[str, Any]	{}	随请求发送的额外元
自定义吧

异步发送和异步接收消息还能不快吗;哈哈哈,直到任务完成消息后才结束,
break!!嘿嘿,终止啦;
同步发送

数据流走向

用户消息→WebSocket服务器→客户端处理→接受信号并聚合→返回

哈哈哈,这是核心啦!不过后面的可以其余人补充,或者我明天再更新,嘿嘿嘿!
耦合,好像有一个特点,async 和 sync这里为啥出来?

核心原因:
异步方法 (async def send):处理WebSocket连接和消息流,避免阻塞主线程
同步包装 (send_sync):为不熟悉异步编程的开发者提供简化接口
消息流处理:LLM响应是流式传输的,异步方式更适合实时接收分块消息
这种设计既保持了性能优势,又提供了使用便利性。
嘿嘿嘿,直接出现!

剩下的咱再议!!!( :nerd_face:,其实是因为我一直在调试,出错了)
1 个赞

艾玛,比我想象中讲解和分享要困难好多呀 :sweat_smile:

哈哈哈,很不好搞。

你这个思路,赶紧实现弄出来玩玩。我就不用彻夜在电脑前看着他了。

我也使用 typescript 写了个 ollama 版本 :nerd_face:

完整代码
// 导入依赖
import { $ } from 'bun'
import ollama, { type ChatResponse } from 'ollama'

const user_input = await console[Symbol.asyncIterator]()
const messages = [] // 存储用户输入和模型回复

// 添加系统提示
messages.push({
    role: 'system', content: `你的目标是完成用户的任务。你必须选择以下的其中一个XML格式进行回复,**一次且仅能输出一个标签**:
- <thought>思考内容</thought>: 先思考要做什么
- <command>命令内容</command>: 需要执行系统命令时输出,内容为纯命令(例如 "echo hello")
- <observation>系统返回结果</observation>: 系统返回的结果,不能自己生成
- <answer>总结内容</answer>: 任务完成时输出总结

重要规则:
1. **每次只能输出一个标签,严禁在同一回复中包含多个标签。**
2. 执行任何命令前,必须先输出 <thought> 标签思考要做什么。
3. 如果你认为需要执行命令,则必须输出 <command> 标签
4. 禁止自己生成 <observation> 标签,这是系统返回的结果。
5. 如果任务已完成,输出 <answer> 标签总结结果。

示例:
user: <task>查看当前目录所在路径</task>
assistant: <thought>我需要查看当前目录所在路径</thought>
assistant: <command>pwd</command>
shell: <observation>/workspaces/mini-agent</observation>
assistant: <answer>当前目录所在路径为:/workspaces/mini-agent</answer>
`
})

while (true) {
    // 获取用户输入
    process.stdout.write('\nyou: ')
    const { value }: { value: string } = await user_input.next()

    // 将用户输入的任务添加到消息列表
    messages.push({ role: 'user', content: `<task>${value.trim()}</task>` })

    // 开始 ReAct 循环
    console.log('\n-------ReAct Start-------')
    while (true) {
        // 调用模型
        const response: ChatResponse = await ollama.chat({
            model: 'qwen3.5:2b',
            messages: messages,
            think: false, // 禁用思考模式
        })

        // 将模型回复添加到消息列表
        const content = response.message.content
        messages.push({ role: 'assistant', content: content })

        const thought = content.match(/<thought>([\s\S]*?)<\/thought>/)?.[1]?.trim() // 提取思考内容
        const command = content.match(/<command>([\s\S]*?)<\/command>/)?.[1]?.trim() // 提取命令
        const answer = content.match(/<answer>([\s\S]*?)<\/answer>/)?.[1]?.trim() // 提取总结

        // 思考要做什么
        if (thought) {
            console.log(`thought:> ${thought}`)
            continue // 有思考内容时,继续循环让模型基于思考内容继续回复
        }

        // 执行命令
        if (command) {
            console.log(`command:> ${command}`) // 打印命令
            try {
                const result = await $`bash -c ${command}`.text()
                messages.push({ role: 'user', content: `<observation>${result}</observation>` }) // 将执行结果添加到消息列表
                console.log(`observation:> ${result}`) // 打印执行结果
            } catch (error) {
                messages.push({ role: 'user', content: `<observation>命令执行失败:${error}</observation>` })
                console.log(`observation:> ${error}`) // 打印执行失败信息
            }
            continue // 有命令执行时,继续循环让模型根据 observation 决定下一步
        }

        // 总结任务
        if (answer) {
            console.log('\n-------ReAct End-------')
            console.log(`answer:> ${answer}`)
            break
        }
    }

}

哈哈哈,没事,慢慢来

哈哈哈,这个是教程,也是我学一点皮毛,和大家分享

嘿嘿,其实,我只是一个思路哈,既然subagent也是一个类似工具提供上下文
你做的这个小Agent如果也可以通过执行器执行并获得结果的话,可以利用subcommand进行调用,也可以集成到工作流里面,嘿嘿

我的这个已经是最经典 ReAct 架构了,包含了思考 → 执行 → 观察 :grin:

就像截图里,先执行了 ls hello.txt 命令结果报错了,因为不存在,然后观察到这个结果又执行了 cat hello.txt,然后还是报错,最终执行了 echo hello world > hello.txt && ls hello.txt 观察到文件夹里有 hello.txt 这个文件后就返回最终答案

不过这个是一个最小实现,正常来说是需要写一些工具函数,让 llm 直接使用 tool call 的能力进行调用,我这里是让 llm 输出 xml 标签,然后手动解析标签,再将标签的命令给 bash 进行执行;要是实现了 tool call,就能实现 mcp,再完善一下提示词注入,实现渐进式披露就是 skill 了

嘿嘿,慢慢改进慢慢来 :grin: