首页 / 文章 / 实用指南:打造可用的迷你人工智能助手的代理、工具与技能

实用指南:打造可用的迷你人工智能助手的代理、工具与技能

《实用指南》操作流程详解:适用于构建可运行迷你 AI 助手的代理、工具与技能;同时为采用该架构的团队提供合同模板、校验机制以及可直接插入的代码片段。

3544 词

本指南将逐步构建从原材料到可运行系统的完整流程,涵盖用于构建功能型迷你人工智能助手的智能体、工具及技能。重点在于可操作的步骤、明确的检查点,以及可直接放入代码仓库的代码,无需猜测其用途。 在概览阶段,应在修改代码之前明确输入参数、各步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行相应步骤,而无需推测隐藏状态。 除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在流程从演示环境过渡到共享环境时出现意外费用。

什么是工具、技能和智能体?

在完成“什么是工具技能”这一阶段时,首先写下相关约定:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持透明。 将配置信息与应用程序代码分开存放。环境文件、密钥存储以及功能开关应集中于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。如果没有这些记录,调试代理将陷入无止境的循环,耗费大量时间。

get_current_conditions
get_forecast
get_alerts
get_historical_weather
get_air_quality
get_marine_conditions
get_river_conditions
get_wildfire_info
search_location
check_service_status
Temperature = Celsius, Fahrenheit, Kelvin

Length / Distance = Inches, Feet, Yards, Miles, Millimeters, Centimeters, Meters, Kilometers

Mass / Weight = Ounces, Pounds, Stone, Grams, Kilograms, Metric Tons

Volume / Capacity = Fluid Ounces, Cups, Pints, Quarts, Gallons, Milliliters, Liters, Cubic Meters

Area =  Square Feet, Square Meters, Acres, Hectares

Speed =  Miles per Hour (mph), Kilometers per Hour (km/h), Knots, Meters per Second (m/s)

Time Zones =  UTC/GMT offsets, Daylight Saving Time (DST) transitions, Unix timestamps to human-readable dates

Storage =  Bytes, Kilobytes (KB), Megabytes (MB), Gigabytes (GB), Terabytes (TB)

使用示例代码进行实验。

在“使用示例代码进行实验”阶段,首先需明确约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合约定。 同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误消息处理都是产品功能的一部分,而非后续需要补充的内容。 需为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。没有这些记录,调试过程将会浪费大量时间。

步骤1 — 导入所需库

在处理“步骤1:导入阶段”时,首先需明确相关规范:所需输入、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改始终符合预期。 建议使用小型、可测试的单元而非庞大的脚本。当某个步骤失败时,故障应指向单一责任点,而非复杂的流程链。 需为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。没有这些记录,调试过程将会浪费大量时间。 在处理“步骤1:导入阶段”时,首先需明确相关规范:所需输入、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改始终符合预期。 除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

import re
import os
import getpass
import requests

第2步 — 创建计算器工具

将第2步的创建阶段视为可度量的对象来处理效果最佳。在扩大范围之前,需记录一个成功的案例、一个失败案例以及回滚说明。 配置应置于应用程序代码之外。环境文件、密钥存储和功能标志应集中存放,以便操作人员无需查看整个系统结构即可进行审计。 以简洁的架构和明确的副作用标签来暴露工具功能。主机需要在自动批准之前知道哪些调用会修改状态。

def calculator_tool(prompt: str) -> str:
    print("    [Tool 1: Calculator] scanning prompt for dollar amounts...")
    dollar_amounts = re.findall(r'\$\s?(\d+(?:\.\d{1,2})?)', prompt)

    if not dollar_amounts:
        result = "no dollar amounts found"
        print(f"    [Tool 1: Calculator] {result}")
        return result

    values = [float(a) for a in dollar_amounts]
    total = sum(values)
    breakdown = " + ".join(f"${v:g}" for v in values)
    result = f"{breakdown} = ${total:.2f}"
    print(f"    [Tool 1: Calculator] found {len(values)} amount(s) {values} -> {result}")
    return result

第3步 — 创建单位转换器工具

将第三步“创建测试场景”视为可度量的表面来处理效果最佳。在扩大范围之前,先记录一个成功的用例、一个失败案例以及回滚说明。同时记录正常流程和恢复流程。重试机制、人工审核环节以及死信处理都是产品的一部分,而非后续需要补充的内容。应提供具有明确结构规范和清晰副作用标签的工具,这样主机在自动批准之前就能知道哪些调用会改变状态。

UNIT_ALIASES = {
    "kilometers": "km", "kilometer": "km", "km": "km",
    "mile": "miles", "miles": "miles",
    "m": "meters", "meter": "meters", "meters": "meters",
    "ft": "feet", "foot": "feet", "feet": "feet",
}

DISTANCE_TO_MILES = {"km": 0.621371, "miles": 1.0, "meters": 0.000621371, "feet": 0.000189394}

def unit_converter_tool(prompt: str) -> str:
    print("    [Tool 2: Unit Converter] scanning prompt for distance legs...")
    legs = re.findall(r'(\d+(?:\.\d+)?)\s*(kilometers?|km|miles?|meters?|feet|ft)\b',
                       prompt, re.IGNORECASE)

    if not legs:
        result = "no distances found"
        print(f"    [Tool 2: Unit Converter] {result}")
        return result

    total_miles = 0.0
    breakdown = []
    for value, unit in legs:
        value = float(value)
        unit_norm = UNIT_ALIASES.get(unit.lower(), unit.lower())
        total_miles += value * DISTANCE_TO_MILES.get(unit_norm, 1.0)
        breakdown.append(f"{value:g} {unit_norm}")
    result = f"{' + '.join(breakdown)} = {round(total_miles, 2)} miles total"
    print(f"    [Tool 2: Unit Converter] found {len(legs)} leg(s) -> {result}")
    return result

第四步 — 创建总结技能

将“第4步:创建阶段”视为可度量的工作面时效果最佳。在扩大范围之前,需记录一份理想运行案例、一个故障案例以及回滚说明。 相比庞大的脚本,应优先选择小型且可测试的单元。当某一步骤出现故障时,故障原因应能指向单一责任模块,而非复杂的流程链。 应使用具有明确结构规范和清晰副作用标注的工具。在自动批准之前,主机需要知道哪些调用会修改状态。 将“第4步:创建阶段”视为可度量的工作面时效果最佳。在扩大范围之前,需记录一份理想运行案例、一个故障案例以及回滚说明。 除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

def summarizer_skill(text: str, max_sentences: int = 2) -> str:
    print("    [Skill: Summarizer] scanning message for key sentence(s)...")
    sentences = [s for s in re.split(r'(?<=[.!?])\s+', text.strip()) if s]

    if len(sentences) <= max_sentences:
        print(f"    [Skill: Summarizer] only {len(sentences)} sentence(s) -- returning as-is")
        return text.strip()

    stopwords = {"the","a","an","is","are","was","were","in","on","at","to","of","and",
                 "or","for","it","this","that","i","you","he","she","they","we","really"}
    words = re.findall(r'\b\w+\b', text.lower())
    freq = {}
    for w in words:
        if w not in stopwords:
            freq[w] = freq.get(w, 0) + 1

    scored = []
    for idx, sentence in enumerate(sentences):
        s_words = re.findall(r'\b\w+\b', sentence.lower())
        score = sum(freq.get(w, 0) for w in s_words)
        scored.append((score, idx, sentence))

    top = sorted(scored, key=lambda x: x[0], reverse=True)[:max_sentences]
    top_in_order = sorted(top, key=lambda x: x[1])
    summary = " ".join(s for _, _, s in top_in_order)
    print(f"    [Skill: Summarizer] kept {len(top_in_order)} of {len(sentences)} sentence(s) -> {summary}")
    return summary

第5步 — 连接到你的LLM

在第五步“连接到阶段”中,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,以便操作人员无需查看整个流程即可进行审计。 当下一步是代码执行或工具调用时,优先使用具有架构验证的结构化输出,而非自由形式的文本描述。

GROQ_API_KEY = os.environ.get("GROQ_API_KEY") or getpass.getpass(
    "Enter your free Groq API key (from https://console.groq.com/keys), "
    "or press Enter to skip: "
)

GROQ_MODEL = "openai/gpt-oss-20b"
GROQ_ENDPOINT = "https://api.groq.com/openai/v1/chat/completions"

def call_llm(augmented_prompt: str,
             system_prompt: str = "You are a helpful, concise assistant.") -> str:
    if not GROQ_API_KEY:
        return ("[No LLM reply -- no Groq API key was provided. Get a free one at "
                 "https://console.groq.com/keys, then re-run the setup cell above.]\n"
                 f"Here is the augmented prompt that would have been sent:\n\"\"\"\n{augmented_prompt}\n\"\"\"")
    try:
        response = requests.post(
            GROQ_ENDPOINT,
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {GROQ_API_KEY}",
            },
            json={
                "model": GROQ_MODEL,
                "messages": [
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": augmented_prompt},
                ],
                "temperature": 0.7,
                "max_tokens": 400,
            },
            timeout=30,
        )
        response.raise_for_status()
        data = response.json()
        return data["choices"][0]["message"]["content"].strip()
    except requests.exceptions.RequestException as e:
        return f"[LLM request failed -- {e}]"
    except (KeyError, IndexError, ValueError):
        return "[LLM returned an unexpected response format.]"

print("LLM configured." if GROQ_API_KEY else "No key entered -- running in fallback mode.")

第六步 — 构建你的智能体

在第六步“构建你的阶段”中,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误消息处理都是产品本身的组成部分,而非后续需要补充的功能。 在网关处进行身份验证,在数据层进行重新授权。仅凭承载令牌并不能作为租户边界。

def show_step(step_num: int, label: str, content: str) -> None:
    """Small helper so every agent prints its pipeline the same, readable way."""
    print(f"\n  STEP {step_num} - {label}:")
    for line in str(content).splitlines() or [""]:
        print(f"    {line}")


def trip_planner_agent(prompt: str) -> str:
    """Agent 1. Condition: 2+ dollar costs AND 2+ distances -- a multi-stop itinerary."""
    print("[Router] -> Agent 1: Trip Planner Agent activated (detected an itinerary: multiple costs + multiple distances)")
    show_step(1, "Original prompt", prompt)

    cost_result = calculator_tool(prompt)
    show_step(2, "Tool result (Calculator -- total cost)", cost_result)

    distance_result = unit_converter_tool(prompt)
    show_step(3, "Tool result (Unit Converter -- total distance)", distance_result)

    augmented_prompt = (
        f"The user asked: \"{prompt}\"\n\n"
        f"A calculator tool already computed the total cost: {cost_result}\n"
        f"A distance tool already computed the total distance traveled: {distance_result}\n\n"
        "Using those two verified totals (don't redo either calculation yourself), give the "
        "user a short, friendly trip summary that reports both totals clearly."
    )
    show_step(4, "Prompt has changed -- now the augmented prompt sent to the LLM", augmented_prompt)

    reply = call_llm(augmented_prompt)
    show_step(5, "Final response from Groq", reply)

    return f"🧳 Agent 1: Trip Planner Agent:\n{reply}"


def text_agent(prompt: str) -> str:
    """Agent 2. Condition: default for any prompt, OR chained after Agent 1
    when the itinerary prompt also has an extra narrative sentence."""
    print("[Router] -> Agent 2: Text Analysis Agent activated")
    show_step(1, "Original prompt", prompt)

    summary = summarizer_skill(prompt)
    show_step(2, "Skill result (Summarizer)", summary)

    augmented_prompt = (
        f"The user wrote: \"{prompt}\"\n\n"
        f"Automatic summary of their message: {summary}\n\n"
        "Write a short, thoughtful, natural-sounding reply to the user that responds "
        "to what they actually said, informed by (but not just repeating) this summary."
    )
    show_step(3, "Prompt has changed -- now the augmented prompt sent to the LLM", augmented_prompt)

    reply = call_llm(augmented_prompt)
    show_step(4, "Final response from Groq", reply)

    return f"📝 Agent 2: Text Analysis Agent:\n{reply}"

第七步 —— 导航至正确的代理

在将第7步流程投入使用之前,需先明确输入参数、该步骤的负责人以及结束标准,然后再进行代码修改。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 相较于庞大的脚本,应优先选择小型且易于测试的单元。当某一步骤失败时,故障原因应能明确指向某个具体的责任模块,而非整个复杂的流程。 在网关处进行身份验证,在数据层再次授权。仅凭承载令牌并不足以界定租户边界。 在将第7步流程投入使用之前,需先明确输入参数、该步骤的负责人以及结束标准,然后再进行代码修改。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 除了功能结果外,还需记录执行时间以及令牌或查询的成本。提前了解成本情况,可避免在流程从演示环境过渡到共享环境时出现意外账单。

def looks_like_itinerary(prompt: str) -> bool:
    dollar_amounts = re.findall(r'\$\s?\d+(?:\.\d{1,2})?', prompt)
    distance_legs = re.findall(r'\d+(?:\.\d+)?\s*(?:kilometers?|km|miles?|meters?|feet|ft)\b',
                                prompt, re.IGNORECASE)
    return len(dollar_amounts) >= 2 and len(distance_legs) >= 2

def has_extra_narrative(prompt: str) -> bool:
    sentences = [s for s in re.split(r'(?<=[.!?])\s+', prompt.strip()) if s]
    extra = [
        s for s in sentences
        if "quot; not in s
        and not re.search(r'\b(?:miles?|km|kilometers?|feet|ft)\b', s, re.IGNORECASE)
        and not s.strip().endswith("?")
    ]
    return len(extra) >= 1

def route_prompt(prompt: str) -> str:
    if looks_like_itinerary(prompt):
        reply = trip_planner_agent(prompt)
        if has_extra_narrative(prompt):
            print("[Router] -> also routing to Agent 2: Text Analysis Agent (extra narrative sentence detected)")
            reply += "\n\n" + text_agent(prompt)
        return reply
    else:
        return text_agent(prompt)

第8步 —— 测试结果

在执行第8步“测试”时,首先需记录下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改不会偏离原有设计。 应将配置信息与应用程序代码分开存放。环境文件、密钥存储以及功能开关应集中于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 需为每次调用记录工具名称、参数哈希值、延迟时间以及最终结果。如果没有这些记录,调试过程将会浪费大量时间。

prompt = input("Ask me anything: ")

print("=" * 60)
print(f"PROMPT: {prompt}")
print("=" * 60)
final_answer = route_prompt(prompt)
print(f"\n  >>> RETURNED: {final_answer}")
print("-" * 60 + "\n")

理解逻辑结构

在“理解逻辑”阶段,首先写下契约内容:所需的输入参数、成功信号以及部分失败时会发生什么。这样的检查清单能确保后续的代码修改保持一致性。 同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续需要补充的功能。 需为每次调用记录工具名称、参数哈希值、延迟时间以及最终结果。没有这些记录,调试代理时将会浪费大量时间。

第一步 — 路由到代理

在完成“第一步:将路由功能接入阶段”的工作时,首先需明确相关规范:所需的输入参数、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合预期。 相比冗长的脚本,更应采用小型且可测试的单元。当某一步骤失败时,故障应指向单一责任模块,而非复杂的流程链。 需为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。若没有这些记录,调试过程将会浪费大量时间。 在完成“第一步:将路由功能接入阶段”的工作时,首先需明确相关规范:所需的输入参数、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合预期。 除了功能结果外,还需记录执行耗时以及代币或查询成本。提前了解成本情况,可避免在功能从演示环境过渡到共享环境时出现意外费用。

第二步 —— 规划代理调用其工具

将第二步的行程规划阶段视为可度量的对象来处理,效果最佳。在扩大范围之前,需记录一个成功的案例、一个失败案例以及回滚说明。 应将配置与应用程序代码分开。环境文件、密钥存储和功能标志应集中存放于一处,以便操作人员无需查看整个系统结构即可进行审计。 需提供具有明确数据结构和清晰副作用标签的工具。主机需要在自动批准之前知道哪些调用会改变系统状态。

第三步 — 调用计算工具

将“第三步:调用阶段”视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。同时记录正常流程和恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的内容。应提供具有明确结构规范和清晰副作用标签的工具,这样主机在自动批准之前就能知道哪些调用会改变状态。

第四步 — 调用单元转换工具

将“第4步:调用阶段”视为可度量的操作面时,其效果最佳。在扩大范围之前,需记录一份理想的执行日志、一个失败案例以及回滚说明。 相比庞大的脚本,应优先使用小型且可测试的单元。当某一步骤失败时,故障应指向单一责任模块,而非复杂的流程链。 应使用结构明确的工具,并标注清晰的副作用。主机需要在自动批准之前知道哪些调用会修改状态。 将“第4步:调用阶段”视为可度量的操作面时,其效果最佳。在扩大范围之前,需记录一份理想的执行日志、一个失败案例以及回滚说明。 除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

第5步 — 创建更新后的提示词

在第五步“创建更新后的阶段”中,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审核。 当下一步是代码执行或工具调用时,应优先使用具有架构验证的结构化输出,而非自由形式的文本描述。

第六步 —— 将结果发送至 GROQ

在第六步“发送结果”阶段,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及死信处理都是产品功能的一部分,而非后续需要补充的内容。应在网关处进行身份验证,在数据层再次授权——仅凭承载令牌并不能作为租户边界。

第七步 — 使用text_agent

在“步骤7:使用textagent”阶段,修改代码之前需先定义输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障原因应能明确指向某个特定责任方,而非整个复杂的流程。应在网关处进行身份验证,在数据层再次授权——仅凭承载令牌并不足以界定租户边界。在“步骤7:使用textagent”阶段,修改代码之前需先定义输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。除了功能结果外,还应记录执行时间以及令牌或查询的成本。提前了解成本情况,可避免在流程从演示环境转向共享环境时出现意外账单。

第8步——使用总结功能

在执行第8步“使用总结”阶段时,首先写下相关约定:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 为每次调用记录工具名称、参数哈希值、延迟时间以及执行结果。如果没有这些记录,调试代理将陷入无止境的循环,耗费大量时间。

第9步——再次将结果发送到GROQ

在执行第9步“发送结果”阶段时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改不会偏离原定方向。 同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续需要补充的功能。 需为每次调用记录工具名称、参数哈希值、延迟时间以及最终结果。没有这些记录,调试代理时往往会浪费大量时间。

阐述使用代理、技能和工具的必要性。

在撰写“为阶段设计提供依据”的内容时,首先需明确合同条款:所需输入、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持透明。 相比冗长的脚本,应优先选择小型且可测试的单元。当某个步骤失败时,故障应指向单一责任点,而非复杂的流程链。 需为每次调用记录工具名称、参数哈希值、延迟时间以及最终结果。没有这些记录,调试循环将会耗费大量时间。 在撰写“为阶段设计提供依据”的内容时,首先需明确合同条款:所需输入、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持透明。 在功能结果旁还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

操作检查清单

将操作检查清单阶段视为可度量的标准,效果最佳。在扩大范围之前,先记录一份完美的操作日志、一个故障案例以及回滚说明。

把这一阶段视为输入与已验证输出之间的契约。为相关文档命名,明确成功标准,绝不允许默许不完整的操作。

使用具有严格结构定义且带有明确副作用标注的工具。主机需要在自动批准之前知道哪些调用会改变系统状态。

对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的连接方式并不等同于业务上的完整性。

编写简短的操作手册:包括如何轮换密钥、如何清空队列、以及如何回滚上一次的导入操作。

同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及死信处理都是产品不可或缺的部分,而非后续需要补充的内容。

在推广该技术栈之前,应先冻结版本、为关键流程记录标准输出日志,并明确回滚步骤。共享环境需要设置速率限制、租户验证机制,以及负责密钥轮换的明确责任人。与其展示花哨的一次性演示,不如追求扎实可靠的性能。

关于6caa2a29578e的批处理说明:请将提供商密钥移出代码仓库,设定单会话令牌上限,并将日志存储在评估用示例文件旁,以便后续模型更换时仍能保持对比性。