Skip to main content

pagesift_api/types/
agent.rs

1//! `/v2/agent` 的请求和结果。
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::error::ApiError;
9
10/// `POST /v2/agent`。字段照 Firecrawl;`allowedHosts` 是 pagesift 扩展。
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct AgentRequest {
14    pub prompt: String,
15    #[serde(default)]
16    pub urls: Option<Vec<String>>,
17    #[serde(default)]
18    pub schema: Option<Value>,
19    #[serde(default)]
20    pub effort: Option<AgentEffort>,
21    /// 只访问 `urls` 里的网址。
22    #[serde(default, rename = "strictConstrainToURLs")]
23    pub strict_constrain_to_urls: bool,
24    /// 额度上限:每抓到一页、每次搜索、每次 map 各算 1。
25    #[serde(default)]
26    pub max_credits: Option<u64>,
27    #[serde(default)]
28    pub webhook: Option<Value>,
29    /// pagesift 扩展:模型只能打开这些主机(含子域名)上的网址。
30    #[serde(default)]
31    pub allowed_hosts: Option<Vec<String>>,
32}
33
34/// 只改变预算上限,不改变停止判据。
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "lowercase")]
37pub enum AgentEffort {
38    Low,
39    Medium,
40    High,
41}
42
43impl AgentEffort {
44    pub fn as_str(self) -> &'static str {
45        match self {
46            Self::Low => "low",
47            Self::Medium => "medium",
48            Self::High => "high",
49        }
50    }
51}
52
53/// 一次运行的结果。
54#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase", default)]
56pub struct AgentOutcome {
57    /// 模型的最后回答。结果不完整时对外放在 `partial`。
58    pub data: Option<Value>,
59    /// 请求带 `schema` 时,`data` 是否符合。
60    pub schema_valid: Option<bool>,
61    /// `answered`(模型自己判断够了)、`coverage_reached`、`saturated`、`no_relevant_pages`(自适应
62    /// 停止判据,和带查询的爬取相同)、`budget_exhausted`、`cancelled`、`error`。
63    pub finish_reason: String,
64    /// 没有缺失、有回答、符合 schema。
65    pub complete: bool,
66    /// 模型报告找不到的内容。
67    pub missing: Vec<String>,
68    pub message: Option<String>,
69    pub sources: Vec<AgentSource>,
70    /// 键是 `data` 里叶子字段的 JSON Pointer。
71    pub field_sources: BTreeMap<String, FieldSource>,
72    pub evidence: Vec<AgentEvidence>,
73    pub steps: Vec<AgentStep>,
74    pub usage: AgentUsage,
75    pub budget: AgentBudget,
76    pub warnings: Vec<String>,
77    pub error: Option<AgentFailure>,
78}
79
80impl AgentOutcome {
81    /// 用掉的额度:每抓到一页、每次搜索、每次 map 各算 1。
82    pub fn credits(&self) -> u64 {
83        self.usage.credits()
84    }
85}
86
87#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase", default)]
89pub struct AgentSource {
90    pub url: String,
91    pub title: Option<String>,
92    pub evidence_ids: Vec<String>,
93    pub content_hash: Option<String>,
94    pub fetched_at: Option<String>,
95}
96
97/// 一个字段来自哪里。`verified`:字段值逐词出现在这些证据的文字里(总结出来的长文字、布尔值不会是真)。
98#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase", default)]
100pub struct FieldSource {
101    pub sources: Vec<String>,
102    pub evidence_ids: Vec<String>,
103    pub verified: bool,
104}
105
106/// 存档的证据:模型看到的文字。
107#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase", default)]
109pub struct AgentEvidence {
110    pub id: String,
111    pub url: String,
112    /// `page`(带 queryFilter 的 scrape)、`search`(搜索结果的摘要)、`extract`(按意图抽取的列表)。
113    pub kind: String,
114    pub query: Option<String>,
115    pub text: String,
116    pub relevance: Option<f32>,
117}
118
119#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase", default)]
121pub struct AgentStep {
122    pub turn: usize,
123    /// `search`、`map`、`scrape`、`extract`。
124    pub tool: String,
125    pub input: Value,
126    /// `ok` 或错误码(`ROBOTS_DISALLOWED` 等)。
127    pub result: String,
128    pub detail: Option<String>,
129}
130
131#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase", default)]
133pub struct AgentUsage {
134    pub pages: u64,
135    pub pages_failed: u64,
136    pub searches: u64,
137    pub maps: u64,
138    pub llm_calls: u64,
139    pub tokens: u64,
140    /// 有的调用服务没报用量,用的是估计值。
141    pub tokens_estimated: bool,
142}
143
144impl AgentUsage {
145    pub fn credits(&self) -> u64 {
146        self.pages + self.searches + self.maps
147    }
148}
149
150#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase", default)]
152pub struct AgentBudget {
153    pub max_pages: u64,
154    pub max_llm_calls: u64,
155    pub max_tokens: u64,
156    pub max_credits: Option<u64>,
157    /// 一次调用的提示上限(模型上下文和每分钟 token 限额推出)。
158    pub input_tokens_per_call: u64,
159    /// 用尽而且结果不完整时:`pages`、`credits`、`quota`、`llmCalls`、`tokens`。
160    pub exhausted: Option<String>,
161}
162
163/// 运行失败的原因(错误码和说明)。
164#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase", default)]
166pub struct AgentFailure {
167    pub code: String,
168    pub message: String,
169}
170
171impl From<&ApiError> for AgentFailure {
172    fn from(e: &ApiError) -> Self {
173        Self {
174            code: e.code.as_str().to_string(),
175            message: e.message.clone(),
176        }
177    }
178}