phi-agent 0.2.6

phi-agent — General-purpose AI Agent framework (builder factory, renderer, config, session management)
Documentation
# Focus — 结构化单任务 LLM 调用

Focus 是一个轻量级原语,用于在主 agent 对话循环之外进行独立的、单一用途的 LLM 调用。它专为**分类、判断和结构化提取**而设计——任何时候你需要 LLM 做出一个专注的决定并返回类型化结果。

## 为什么需要 Focus?

在正常的 agent 对话中,LLM 同时管理工具、上下文和多轮推理。但有时候你只需要一个特定问题的答案:

- "这个终端输出正常,还是表示有错误?"
- "将这个用户请求分类为:提问、命令或闲聊。"
- "从这段文本中提取关键实体为结构化 JSON。"

把这些判断丢进主 agent 循环会增加噪音。Focus 将它们分解为独立的调用——一个系统提示、一个输入、一个类型化输出。如果业务分解得当,即使是弱模型也能做好一件事。

## 快速示例

```rust
use std::sync::Arc;
use std::time::Duration;
use phi_agent::{Focus, OpenAiClient};
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct Sentiment {
    sentiment: String,  // "positive", "negative", "neutral"
    confidence: f64,    // 0.0 到 1.0
}

async fn classify_sentiment(client: Arc<OpenAiClient>, text: String) -> Result<Sentiment, FocusError> {
    let focus = Focus::new(
        client,
        "你是一个情感分类器。分析文本并返回 JSON: \
         {\"sentiment\": \"positive|negative|neutral\", \"confidence\": 0.0-1.0}",
    );

    let output = focus.ask::<Sentiment>(&text, Duration::from_secs(10)).await?;
    Ok(output.result)
}
```

就这样。没有 agent 循环,没有工具注册——只有一次专注的调用和一个类型化的返回值。

## 核心概念

### 1. Focus

`Focus` 在创建时将 LLM 客户端绑定到一个**系统提示**。系统提示描述角色和期望的输出格式。一旦创建,系统提示不再改变——一个 `Focus` 实例只做一件事。

```rust
pub struct Focus {
    // 持有 Arc<dyn LlmClient> + system_prompt (私有)
}

impl Focus {
    pub fn new(client: Arc<dyn LlmClient>, system_prompt: impl Into<String>) -> Self;
    pub async fn ask<T: DeserializeOwned>(&self, input: &impl FocusInput, timeout: Duration)
        -> Result<FocusOutput<T>, FocusError>;
}
```

- **`new()`** 很廉价——多个 Focus 实例可以共享同一个 LLM 客户端。
- **`ask::<T>()`** 发送系统提示 + 用户输入,强制 JSON 输出模式,并反序列化到你的类型 `T`- **超时**是显式的——你控制等待多久。

### 2. FocusInput

任何可以格式化到用户提示的内容:

| 输入 | 使用场景 |
|-------|----------|
| `&str` / `String` | 单段需要分类或判断的文本 |
| `FocusContext` | 多个相关字段(如终端输出 + 耗时 + 命令) |

### 3. FocusContext(结构化输入)

当需要发送多个带标签的字段时:

```rust
use phi_agent::FocusContext;

let ctx = FocusContext::new()
    .add("command", "apt install nginx")
    .add("elapsed", "30s")
    .add("screen", "Reading package lists...\nBuilding dependency tree...");

let output = focus.ask::<TaskStatus>(&ctx, Duration::from_secs(5)).await?;
```

字段在发送给 LLM 前格式化为 `【key】\nvalue`,每个标签作为模型的上下文。

### 4. FocusOutput\<T\>

返回值包含结构化结果和原始响应:

```rust
pub struct FocusOutput<T> {
    pub result: T,           // 从 JSON 反序列化
    pub raw_response: String, // 原始 LLM 输出(用于调试)
}
```

保留 `raw_response` 用于日志——解析失败时,它告诉你 LLM 到底返回了什么。

### 5. FocusError

三种失败模式,全部明确:

```rust
pub enum FocusError {
    Timeout(Duration),                     // LLM 未及时响应
    Llm(String),                           // 网络错误、API 错误等
    Parse { error: String, raw: String },  // LLM 未返回匹配 T 的有效 JSON
}
```

## Focus vs. Agent 的使用场景

| 场景 | 使用 |
|------|------|
| 带工具的多轮对话 | Agent (`PhiAgent::run_turn`) |
| 一次性分类或判断 | Focus |
| 从文本中结构化提取 | Focus |
| Agent 循环外的预处理/后处理 | Focus |
| 简单的"完成了吗?"/"什么状态?"检查 | Focus |

常见模式:在工具实现中使用 Focus 作为**辅助调用**。你的工具完成机械工作(运行命令、获取数据),然后用 Focus 解释结果。

## 完整示例

参见 [`examples/focus-demo.rs`](https://github.com/hibuka-labs/phi-agent/blob/master/examples/focus-demo.rs) 获取完整的可运行示例。

## API 参考

Focus 类型从 phi-agent 重新导出:

```rust
pub use agent_works::focus::{
    Context as FocusContext,
    Focus,
    FocusError,
    FocusInput,
    FocusOutput,
};
```

详细的 API 文档见 [docs.rs/phi-agent](https://docs.rs/phi-agent)。