feat: AI 工具架构重构与微信增强 (0.2.17 → 0.2.22)

0.2.17 收束模型初始工具列表:
- 仅暴露 query_capabilities/query_capability_detail/call_capability 三个入口
- API 能力统一通过 iAs HTTP API 封装,删除重复 YAML 工具

0.2.18 系统提示词拆分:
- 固定系统提示词硬编码,动态附加部分 AI 可维护
- 新增 update_additional_system_prompt 能力和持久化

0.2.19 上下文增强与打断语义:
- /new 拥有最高打断语义,取消运行中任务并清除当前 turn
- raw_message JSONB 原文入库,群聊按 group_id 隔离作用域
- 工具调用说明替代动态附加系统提示词

0.2.20 微信输入中状态:
- 改用官方 getconfig + sendtyping 流程,显式管理 typing 状态

0.2.21 API 文档功能分组:
- /v1/api-docs 返回 groups,帮助 AI 按功能面发现接口

0.2.22 AI API 能力去重:
- 移除与 HTTP endpoint 重复的专用能力,统一用 query_api_docs + call_http_api
- 删除不再编译的 web/memory 工具文件
This commit is contained in:
2026-07-08 14:58:13 +08:00
parent 279d89b87a
commit 7d5140c8d2
49 changed files with 2330 additions and 1185 deletions
+30 -7
View File
@@ -32,6 +32,8 @@ interface MessageRow {
content: string;
tool_call_id?: string;
tool_calls?: string;
raw_message?: unknown;
external_message_id?: string;
created_at: string;
}
@@ -58,8 +60,9 @@ interface SimulatorResponse {
has_query: boolean;
scope_key: string;
tools: Tool[];
default_messages: { role: string; content: string }[];
default_messages: Array<Record<string, unknown>>;
default_system_prompt: string;
tool_call_instruction?: string;
sessions: SessionRow[];
messages: MessageRow[];
memories: MemoryRow[];
@@ -92,15 +95,17 @@ export function ContextSimulatorPage() {
const channel = searchParams.get("channel") || "wechat";
const accountId = searchParams.get("account_id") || "";
const fromUserId = searchParams.get("from_user_id") || "";
const groupId = searchParams.get("group_id") || "";
// 编辑器状态
const [messages, setMessages] = useState<{ role: string; content: string }[]>([]);
const [messages, setMessages] = useState<Array<Record<string, unknown>>>([]);
const [selectedTools, setSelectedTools] = useState<Set<string>>(new Set());
const loadData = (values: {
channel: string;
accountId: string;
fromUserId: string;
groupId: string;
}) => {
setLoading(true);
setError(null);
@@ -108,6 +113,7 @@ export function ContextSimulatorPage() {
if (values.channel) params.channel = values.channel;
if (values.accountId) params.account_id = values.accountId;
if (values.fromUserId) params.from_user_id = values.fromUserId;
if (values.groupId) params.group_id = values.groupId;
apiGet<SimulatorResponse>("/v1/context/simulator", params)
.then((res) => {
@@ -120,8 +126,8 @@ export function ContextSimulatorPage() {
};
useEffect(() => {
loadData({ channel, accountId, fromUserId });
}, [channel, accountId, fromUserId]);
loadData({ channel, accountId, fromUserId, groupId });
}, [channel, accountId, fromUserId, groupId]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@@ -130,9 +136,11 @@ export function ContextSimulatorPage() {
const c = (form.get("channel") as string) || "";
const a = (form.get("account_id") as string) || "";
const f = (form.get("from_user_id") as string) || "";
const g = (form.get("group_id") as string) || "";
if (c) params.set("channel", c);
if (a) params.set("account_id", a);
if (f) params.set("from_user_id", f);
if (g) params.set("group_id", g);
setSearchParams(params);
};
@@ -238,6 +246,15 @@ export function ContextSimulatorPage() {
className="w-32 border border-gray-300 rounded px-2.5 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-500 mb-0.5">group_id</label>
<input
name="group_id"
defaultValue={groupId}
placeholder="group"
className="w-32 border border-gray-300 rounded px-2.5 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
/>
</div>
<button
type="submit"
className="bg-blue-600 text-white px-4 py-1.5 rounded text-sm font-medium hover:bg-blue-700 transition"
@@ -267,7 +284,13 @@ export function ContextSimulatorPage() {
<span> {data.messages.length} </span>
<span> {data.memories.length} </span>
<span> {data.summaries.length} </span>
<span> {data.tool_call_instruction ? "已配置" : "未配置"}</span>
</div>
{data.tool_call_instruction && (
<div className="mt-2 text-green-700 whitespace-pre-wrap">
{data.tool_call_instruction}
</div>
)}
</div>
)}
@@ -315,7 +338,7 @@ export function ContextSimulatorPage() {
<span className="text-sm font-medium text-gray-800">{tool.name}</span>
<span
className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${
tool.source === "内置"
tool.source === "入口"
? "bg-blue-100 text-blue-700"
: "bg-orange-100 text-orange-700"
}`}
@@ -369,7 +392,7 @@ export function ContextSimulatorPage() {
<div className="flex items-center gap-2">
<select
className="text-xs border rounded px-1.5 py-0.5 bg-white"
value={msg.role}
value={String(msg.role || "user")}
onChange={(e) => {
const next = [...messages];
next[i] = { ...next[i], role: e.target.value };
@@ -397,7 +420,7 @@ export function ContextSimulatorPage() {
<textarea
className="w-full border rounded px-2 py-1.5 text-sm font-mono bg-white resize-y"
rows={3}
value={msg.content}
value={String(msg.content || "")}
onChange={(e) => {
const next = [...messages];
next[i] = { ...next[i], content: e.target.value };
+48 -14
View File
@@ -6,11 +6,13 @@ interface ToolParam {
param_type: string;
required: boolean;
description: string;
default?: string;
}
interface ToolItem {
name: string;
description: string;
category: string;
source: string;
path?: string;
params: ToolParam[];
@@ -21,8 +23,9 @@ interface ToolsResponse {
success: boolean;
total: number;
tools_path: string;
builtin_tools: ToolItem[];
capability_tools: ToolItem[];
entry_tools: ToolItem[];
api_tools: ToolItem[];
http_tools: ToolItem[];
warnings: string[];
}
@@ -56,6 +59,12 @@ export function ToolsPage() {
if (!data) return null;
const sourceClass = (item: ToolItem) => {
if (item.category === "entry") return "bg-slate-100 text-slate-700";
if (item.category === "api") return "bg-blue-50 text-blue-700";
return "bg-emerald-50 text-emerald-700";
};
const renderToolCard = (item: ToolItem) => (
<article
key={item.name}
@@ -70,7 +79,11 @@ export function ToolsPage() {
{item.description}
</p>
</div>
<span className="shrink-0 inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-semibold bg-blue-50 text-blue-700">
<span
className={`shrink-0 inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-semibold ${sourceClass(
item
)}`}
>
{item.source}
</span>
</div>
@@ -102,6 +115,11 @@ export function ToolsPage() {
</span>
)}
{param.default && (
<span className="text-[11px] font-medium text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
{param.default}
</span>
)}
</div>
<p className="mt-1 text-sm text-gray-500">{param.description}</p>
</div>
@@ -131,7 +149,7 @@ export function ToolsPage() {
<div className="mb-7">
<h1 className="text-2xl md:text-3xl font-bold tracking-tight"></h1>
<p className="mt-1.5 text-sm text-gray-500">
{data.total} YAML
{data.total} HTTP
<code className="text-xs bg-gray-200 text-gray-700 px-1.5 py-0.5 rounded">
{data.tools_path}
</code>
@@ -153,35 +171,51 @@ export function ToolsPage() {
<section className="mb-8">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold"></h2>
<span className="text-sm text-gray-400">{data.builtin_tools.length} </span>
<h2 className="text-lg font-semibold"></h2>
<span className="text-sm text-gray-400">{data.entry_tools.length} </span>
</div>
<div className="flex flex-col gap-3">
{data.builtin_tools.length === 0 ? (
{data.entry_tools.length === 0 ? (
<div className="bg-white rounded-lg border border-gray-200 p-5 text-sm text-gray-400">
</div>
) : (
data.builtin_tools.map(renderToolCard)
data.entry_tools.map(renderToolCard)
)}
</div>
</section>
<section className="mb-8">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold">YAML </h2>
<span className="text-sm text-gray-400">{data.capability_tools.length} </span>
<h2 className="text-lg font-semibold">API </h2>
<span className="text-sm text-gray-400">{data.api_tools.length} </span>
</div>
<div className="flex flex-col gap-3">
{data.capability_tools.length === 0 ? (
{data.api_tools.length === 0 ? (
<div className="bg-white rounded-lg border border-gray-200 p-5 text-sm text-gray-400">
</div>
) : (
data.capability_tools.map(renderToolCard)
data.api_tools.map(renderToolCard)
)}
</div>
</section>
<section className="mb-8">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold">HTTP </h2>
<span className="text-sm text-gray-400">{data.http_tools.length} </span>
</div>
<div className="flex flex-col gap-3">
{data.http_tools.length === 0 ? (
<div className="bg-white rounded-lg border border-gray-200 p-5 text-sm text-gray-400">
</div>
) : (
data.http_tools.map(renderToolCard)
)}
</div>
</section>
</div>
);
}
}