从零开始学习 Agent 开发 —— smolagents 框架深度分析(一):cli.py

smolagents框架是一个极简的 ReAct agent 框架,比较适合我们这种零基础的同学入门 agent 开发,学习并理解 anegt 的设计逻辑。

入口函数——main()

观察入口函数可以让我们快速了解其流程:

def main() -> None:
    args = parse_arguments()

    # Check if we should run in interactive mode
    # Interactive mode is triggered when no prompt is provided
    if args.prompt is None:
        prompt, tools, model_type, model_id, provider, api_base, api_key, imports, action_type = interactive_mode()
    else:
        prompt = args.prompt
        tools = args.tools
        model_type = args.model_type
        model_id = args.model_id
        provider = args.provider
        api_base = args.api_base
        api_key = args.api_key
        imports = args.imports
        action_type = args.action_type

    run_smolagent(
        prompt,
        tools,
        model_type,
        model_id,
        provider=provider,
        api_base=api_base,
        api_key=api_key,
        imports=imports,
        action_type=action_type,
    )

可以看到,main()方法就只是拿到启动参数,然后调用run_smolagent()方法。

核心方法——run_smolagent()

这 40 行是整个文件的价值所在。

1. 加载环境变量

load_dotenv()

先调用load_dotenv()方法,从.env文件中加载环境变量,把项目根目录 .env 文件的内容读进 os.environ:

def load_dotenv(
    dotenv_path: Optional[StrPath] = None,
    stream: Optional[IO[str]] = None,
    verbose: bool = False,
    override: bool = False,
    interpolate: bool = True,
    encoding: Optional[str] = "utf-8",
) -> bool:
    """Parse a .env file and then load all the variables found as environment variables.

    Parameters:
        dotenv_path: Absolute or relative path to .env file.
        stream: Text stream (such as `io.StringIO`) with .env content, used if
            `dotenv_path` is `None`.
        verbose: Whether to output a warning the .env file is missing.
        override: Whether to override the system environment variables with the variables
            from the `.env` file.
        interpolate: Whether to interpolate variables using POSIX variable expansion.
        encoding: Encoding to be used to read the file.
    Returns:
        Bool: True if at least one environment variable is set else False

    If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
    .env file with its default parameters. If you need to change the default parameters
    of `find_dotenv()`, you can explicitly call `find_dotenv()` and pass the result
    to this function as `dotenv_path`.

    If the environment variable `PYTHON_DOTENV_DISABLED` is set to a truthy value,
    .env loading is disabled.
    """
    if _load_dotenv_disabled():
        logger.debug(
            "python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable"
        )
        return False

    if dotenv_path is None and stream is None:
        dotenv_path = find_dotenv()

    dotenv = DotEnv(
        dotenv_path=dotenv_path,
        stream=stream,
        verbose=verbose,
        interpolate=interpolate,
        override=override,
        encoding=encoding,
    )
    return dotenv.set_as_environment_variables()

之后使用.env文件中的配置项,用os.getenv()方法即可,例如:

import os
from dotenv import load_dotenv

load_dotenv()                        # ① 只需调用一次,放在最开头
key = os.getenv("HF_API_KEY")        # ② 之后随处取用

接下来的load_model()方法中会用到.env文件中的配置项。

2. 构造模型

这里的实现简单粗暴,兼容了4中模型配置方式:

model = load_model(model_type, model_id, api_base=api_base, api_key=api_key, provider=provider)
def load_model(
    model_type: str,
    model_id: str,
    api_base: str | None = None,
    api_key: str | None = None,
    provider: str | None = None,
) -> Model:
    if model_type == "OpenAIModel":
        return OpenAIModel(
            api_key=api_key or os.getenv("FIREWORKS_API_KEY"),
            api_base=api_base or "https://api.fireworks.ai/inference/v1",
            model_id=model_id,
        )
    elif model_type == "LiteLLMModel":
        return LiteLLMModel(
            model_id=model_id,
            api_key=api_key,
            api_base=api_base,
        )
    elif model_type == "TransformersModel":
        return TransformersModel(model_id=model_id, device_map="auto")
    elif model_type == "InferenceClientModel":
        return InferenceClientModel(
            model_id=model_id,
            token=api_key or os.getenv("HF_API_KEY"),
            provider=provider,
        )
    else:
        raise ValueError(f"Unsupported model type: {model_type}")

通过if-elif提供了四种选择:

model_type说明
InferenceClientModelHF Inference API,CLI 默认,读 HF_API_KEY
OpenAIModelOpenAI 兼容接口。注意默认 api_base 指向 Fireworks,不是 OpenAI 官方
LiteLLMModelLiteLLM 代理,一个接口打通上百家厂商
TransformersModel本地推理,需要 torch + 显卡

3. 构造工具

这一步开始准备工具集,给模型使用,工具有本地工具和远程工具,通过不同的方式获取:

    available_tools = []

    for tool_name in tools:
        if "/" in tool_name:
            space_name = tool_name.split("/")[-1].lower().replace("-", "_").replace(".", "_")
            description = f"Tool loaded from Hugging Face Space: {tool_name}"
            available_tools.append(Tool.from_space(space_id=tool_name, name=space_name, description=description))
        else:
            if tool_name in TOOL_MAPPING:
                available_tools.append(TOOL_MAPPING[tool_name]())
            else:
                raise ValueError(f"Tool {tool_name} is not recognized either as a default tool or a Space.")

如果是远程工具的话,用Tool.from_space()方法获取,如果是本地工具的话,就直接从 TOOL_MAPPING 里拿到工具的类,然后自己实例化。

我们可以看看 TOOL_MAPPING 的定义:

TOOL_MAPPING = {
    tool_class.name: tool_class
    for tool_class in [
        PythonInterpreterTool,
        DuckDuckGoSearchTool,
        VisitWebpageTool,
    ]
}

可以看到,默认是三个工具,那么 TOOL_MAPPING 的结构应该是这样:

{
    'python_interpreter': <class 'smolagents.default_tools.PythonInterpreterTool'>,
    'web_search':         <class 'smolagents.default_tools.DuckDuckGoSearchTool'>,
    'visit_webpage':      <class 'smolagents.default_tools.VisitWebpageTool'>,
}

所以实际上就是,我们配置使用哪些工具,它就给工具集添加哪些工具,而不是一股脑把本地工具全添加进去。

4. 构造Agent

这是最后一部,构造 Agent ,这里提供了两种 Agent 类型, CodeAgent 和 ToolCallingAgent ,官方推荐使用 CodeAgent , CodeAgent 能在沙盒里写代码, ToolCallingAgent 只能调用工具并且让工具输出。

    if action_type == "code":
        agent = CodeAgent(
            tools=available_tools,
            model=model,
            additional_authorized_imports=imports,
            stream_outputs=True,
        )
    elif action_type == "tool_calling":
        agent = ToolCallingAgent(tools=available_tools, model=model, stream_outputs=True)
    else:
        raise ValueError(f"Unsupported action type: {action_type}")

我们会另开一篇博客,深度研究 CodeAgent 和 ToolCallingAgent 。

5. 启动Agent

最后,调用 agent.run() 方法,让 Agent 跑起来,流程就全部结束了。

    agent.run(prompt)

结语

我们看完了 cli.py ,它是一个真正可用的产品功能,能够根据用户的输入组装工具、启动 Agent ,下一步我们会一起深度研究,smolagents 框架是如何实现一个 Agent 的。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇