@flutchai/聊天核心
Flutch Chat API的框架-不可知客户端和状态:打出请求,SSE流,对话状态和存储. 没有UI。 说对了 @flutchai/聊天反应 或建立自己的接口.
import { createFlutchClient, createChatStore } from "@flutchai/chat-core";
const client = createFlutchClient({ baseUrl: "/api/chat", agentId: "…" }); // your backend knows the user;
const store = createChatStore({ client });
store.subscribe(s => render(s.messages));
store.getState().setDraftText("Hello");
await store.getState().send();
没有商店,直接使用客户端: invoke, (中文(简体) ), stream (类型事件), send (累积文本), cancel, (中文(简体) ), listThreads, (中文(简体) ), getMessages, (中文(简体) ), react, (中文(简体) ), upload, (中文(简体) ), getArticle (作为《京都议定书》缔约方会议的 article_link。 API 类型 @flutchai/chat-contract。 。 。 .
存储:通过 storage (单位:千美元)get/set/remove,同步或同步)改为 createChatStore 用于反应原生,饼干等. 默认是 localStorage 记忆回落.
后端代理服务器
永远不要从浏览器中调用 Flutch API . API密钥识别出你的公司 您的后端接收浏览器的请求, 添加密钥和用户, 并转发它们 。 客户端镜像 外部 API 路径 baseUrl,所以代理是一个薄的通过:
浏览器调用 {baseUrl} + 键 | 转发至 https://api.flutch.ai/api/v1 + 键 |
|---|---|
POST /agents/{agentId}/stream | 相同的路径, SSE |
POST /agents/{agentId}/invoke | 相同的路径 |
POST /agents/{agentId}/cancel | 相同的路径 |
GET /agents/{agentId}/threads | 相同的路径 |
GET /agents/{agentId}/threads/{id}/messages | 相同的路径 |
GET /agents/{agentId}/messages/{id}/stream | 相同的路径, SSE, 通过 Last-Event-ID |
PATCH /agents/{agentId}/messages/{id}/reactions | 相同的路径 |
POST /agents/{agentId}/uploads | 相同路径, 多段 |
GET /agents/{agentId}/articles/{id} | 相同的路径 |
代理对每个请求必须做什么:
- 认证浏览器用户 与你自己的会话.
- 设定
X-API-Key从服务器侧配置. - 设定
externalUserId本身 (JSON身体为invoke编号 :stream,其他所有项目的查询字符串),从认证的会话:您自己的id给用户,例如.(string) $request->user()->id. Flutch 第一次看到 id 时创建用户,所以没有注册或存储的内容. 永远不要相信一个externalUserId或者说userId由浏览器发送,或者一个用户可以读取另一个的对话. - 只允许您想要的代理 内
{agentId},只有以上路线. - 无缓冲流 对于两个SSE路线,并给他们一个很长的时间(15分钟):一个运行其他特工的答案需要几分钟. 恢复路线上的404使聊天放弃了仍在生成的回覆.
- 前进
cancel仅用于停止按钮. 关闭页面或失去网络的用户不会停止回复:Flutch完成并保存在线程中,历史终点返回后全部返回. 客户打电话cancel仅当用户按住 Stop.
草图
// routes/api.php
Route::middleware('auth')->prefix('chat/agents/{agent}')->group(function () {
Route::post('stream', [FlutchProxy::class, 'stream']);
Route::post('invoke', [FlutchProxy::class, 'invoke']);
Route::post('cancel', [FlutchProxy::class, 'cancel']);
Route::get('threads', [FlutchProxy::class, 'threads']);
Route::get('threads/{thread}/messages', [FlutchProxy::class, 'messages']);
Route::patch('messages/{message}/reactions', [FlutchProxy::class, 'react']);
});
class FlutchProxy
{
private const BASE = 'https://api.flutch.ai/api/v1';
private function http(string $agent)
{
abort_unless($agent === config('services.flutch.agent_id'), 404);
return Http::withHeaders(['X-API-Key' => config('services.flutch.key')]);
}
private function userId(Request $r): string
{
return (string) $r->user()->id; // your own id, from your session, never from the request
}
private function json($res)
{
return response($res->body(), $res->status())->header('Content-Type', 'application/json');
}
public function stream(Request $r, string $agent)
{
$body = $r->only(['content', 'threadId', 'metadata']) + ['externalUserId' => $this->userId($r)];
$upstream = $this->http($agent)->withOptions(['stream' => true])
->post(self::BASE."/agents/$agent/stream", $body);
return response()->stream(function () use ($upstream) {
$body = $upstream->toPsrResponse()->getBody();
while (!$body->eof() && !connection_aborted()) {
echo $body->read(1024);
@ob_flush();
flush();
}
$body->close(); // does not stop the reply: Flutch finishes it and saves it in the thread
}, $upstream->status(), [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no', // nginx
]);
}
public function invoke(Request $r, string $agent)
{
$body = $r->only(['content', 'threadId', 'metadata']) + ['externalUserId' => $this->userId($r)];
return $this->json($this->http($agent)->post(self::BASE."/agents/$agent/invoke", $body));
}
public function cancel(Request $r, string $agent)
{
return $this->json($this->http($agent)->post(
self::BASE."/agents/$agent/cancel?externalUserId=".urlencode($this->userId($r)),
$r->only(['requestId'])
));
}
public function threads(Request $r, string $agent)
{
return $this->json($this->http($agent)->get(
self::BASE."/agents/$agent/threads",
$r->only(['page', 'limit']) + ['externalUserId' => $this->userId($r)]
));
}
public function messages(Request $r, string $agent, string $thread)
{
return $this->json($this->http($agent)->get(
self::BASE."/agents/$agent/threads/$thread/messages",
$r->only(['page', 'limit']) + ['externalUserId' => $this->userId($r)]
));
}
public function react(Request $r, string $agent, string $message)
{
return $this->json($this->http($agent)->patch(
self::BASE."/agents/$agent/messages/$message/reactions?externalUserId=".urlencode($this->userId($r)),
$r->only(['like'])
));
}
}
上传( E)POST uploads)是一个多部分的通过: 附加上传的文件 Http::attach('file', ...) 和同样的 externalUserId 查询字符串.
这是一个起点,而不是测试的滴入代码。 调整错误处理并确保 PHP 输出缓冲( S)output_buffering=off, (中文(简体) ), zlib.output_compression=off),任何倒置代理都不缓冲 /stream 反应.