mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
feat: 实现多语言文档架构
- 创建 docs/zh/ 和 docs/en/ 目录结构 - 迁移文档至多语言目录,添加中英文切换链接 - 创建 README_EN.md 英文版本 - 更新 README.md 添加语言切换链接 - 删除旧文档和临时文件
This commit is contained in:
10
README.md
10
README.md
@ -1,5 +1,7 @@
|
|||||||
# HomeAgent
|
# HomeAgent
|
||||||
|
|
||||||
|
> **English**: [README_EN.md](./README_EN.md)
|
||||||
|
|
||||||
首个提出**核心域与应用域分离**的 Agent 框架。内核零 IO,一切外界交互由插件承载——WebUI、QQ、命令行、文件操作、网络搜索、备忘,全部是插件,内核不碰任何 IO。
|
首个提出**核心域与应用域分离**的 Agent 框架。内核零 IO,一切外界交互由插件承载——WebUI、QQ、命令行、文件操作、网络搜索、备忘,全部是插件,内核不碰任何 IO。
|
||||||
|
|
||||||
配合**三层记忆架构**(Context → Document → Graph),单对话长期稳定运行,记忆不衰减。
|
配合**三层记忆架构**(Context → Document → Graph),单对话长期稳定运行,记忆不衰减。
|
||||||
@ -166,10 +168,10 @@ internal/
|
|||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
- [项目概览](docs/OVERVIEW.md)
|
- [项目概览](docs/zh/OVERVIEW.md) | [English](docs/en/OVERVIEW.md)
|
||||||
- [技术架构](docs/ARCHITECTURE.md)
|
- [技术架构](docs/zh/ARCHITECTURE.md) | [English](docs/en/ARCHITECTURE.md)
|
||||||
- [插件开发指南](docs/PLUGIN_DEV.md)
|
- [插件开发指南](docs/zh/PLUGIN_DEV.md) | [English](docs/en/PLUGIN_DEV.md)
|
||||||
- [Lua Adapter](docs/ADAPTER.md)
|
- [Lua Adapter](docs/zh/ADAPTER.md) | [English](docs/en/ADAPTER.md)
|
||||||
- [知识库演示](knowledge/homeagent_architecture/content.md)
|
- [知识库演示](knowledge/homeagent_architecture/content.md)
|
||||||
|
|
||||||
## 构建
|
## 构建
|
||||||
|
|||||||
185
README_EN.md
Normal file
185
README_EN.md
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
# HomeAgent
|
||||||
|
|
||||||
|
> **中文**: [README.md](./README.md)
|
||||||
|
|
||||||
|
The first Agent framework to propose **separation of core domain and application domain**. The kernel performs zero IO — all external interaction is handled by plugins: WebUI, QQ, CLI, file operations, web search, memos — everything is a plugin, the kernel doesn't touch any IO.
|
||||||
|
|
||||||
|
Combined with a **three-layer memory architecture** (Context → Document → Graph), it achieves stable long-running single-conversation operation without memory decay.
|
||||||
|
|
||||||
|
```go
|
||||||
|
homed (kernel, zero IO) ← PluginSDK → plugins (all IO capabilities)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Innovations
|
||||||
|
|
||||||
|
**Separation of Core Domain and Application Domain** — The kernel only handles LLM orchestration, memory management, and knowledge retrieval; all IO capabilities (sending/receiving messages, reading/writing files, network requests, hardware interaction) are implemented by plugins. Plugins can be hot-loaded, independently developed, and independently released. This is not a microservice split of an RPC framework, but a domain-level separation in Agent framework design.
|
||||||
|
|
||||||
|
**Three-Layer Memory Architecture** — Solves the memory decay problem for long-running agents:
|
||||||
|
- **Context Layer**: TF-IDF relevance-scored event window, maintains recent topK context entries
|
||||||
|
- **Document Layer**: Temporary memory with automatic cold data sinking, also supports user-initiated submissions
|
||||||
|
- **Graph Layer**: SQLite graph database, persists entity relationships and semantic memory, supports distillation pipelines to extract triples from conversations
|
||||||
|
|
||||||
|
## Architecture Diagrams
|
||||||
|
|
||||||
|
### 1. Message Processing Sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant U as User/Plugin
|
||||||
|
participant IO as IOManager
|
||||||
|
participant EV as eventLoop
|
||||||
|
participant CTX as RelevanceContext
|
||||||
|
participant LLM as LLM+Tool Loop
|
||||||
|
participant ST as StageHost
|
||||||
|
participant MEM as Three-Layer Memory
|
||||||
|
|
||||||
|
U->>IO: InjectInput(type, payload)
|
||||||
|
IO->>EV: inputCh
|
||||||
|
rect lavender
|
||||||
|
Note over EV: processTextInput
|
||||||
|
EV->>ST: StageOnInput Plugin can rewrite/short-circuit
|
||||||
|
EV->>CTX: Prune(input,topK) TF-IDF pruning
|
||||||
|
CTX->>MEM: Low-score events archived to Document
|
||||||
|
EV->>CTX: Append(input) 5s debounce write
|
||||||
|
end
|
||||||
|
rect lightgreen
|
||||||
|
Note over EV,LLM: process()
|
||||||
|
EV->>MEM: buildMemoryContext Indexer recalls from Graph
|
||||||
|
EV->>MEM: buildSystemPrompt Persona+Memory+Skills injection
|
||||||
|
EV->>ST: StagePreAction Plugin can pre-intercept
|
||||||
|
loop Tool loop
|
||||||
|
LLM->>LLM: drainInterrupts
|
||||||
|
LLM->>LLM: LLM Chat
|
||||||
|
LLM->>ST: StagePostAction Plugin can modify/short-circuit
|
||||||
|
alt No tool call
|
||||||
|
LLM-->>EV: Returns response
|
||||||
|
else
|
||||||
|
loop Each tool
|
||||||
|
ST->>ST: StageBeforeToolcall Plugin can reject
|
||||||
|
LLM->>LLM: executeToolCall
|
||||||
|
ST->>ST: StageAfterToolcall
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rect lightpink
|
||||||
|
Note over EV: emitResponse
|
||||||
|
CTX->>CTX: Append(response)
|
||||||
|
ST->>ST: StageBeforeOutput Plugin can rewrite
|
||||||
|
EV-->>U: ResponseCh CLI sync
|
||||||
|
EV-->>EV: Event bus WebUI SSE
|
||||||
|
ST->>ST: StageAfterOutput Read-only
|
||||||
|
EV->>MEM: emitMemoryCandidate
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Stage Pipeline
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
S1[① on_input] --> S2[② pre_action]
|
||||||
|
S2 --> S3[③ post_action]
|
||||||
|
S3 --> Q{Has tool?}
|
||||||
|
Q -->|Yes| S4[④ before_toolcall]
|
||||||
|
S4 --> T[executeToolCall]
|
||||||
|
T --> S5[⑤ after_toolcall]
|
||||||
|
S5 --> S3
|
||||||
|
Q -->|No| S6[⑥ before_output]
|
||||||
|
S6 --> S7[⑦ after_output]
|
||||||
|
style S1 fill:#e1f5fe
|
||||||
|
style S3 fill:#fff3e0
|
||||||
|
style S6 fill:#e8f5e9
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Three-Layer Memory
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph C[① Context Working Window]
|
||||||
|
RC[RelevanceContext]
|
||||||
|
A[Append] -->|Vectorize char 1-2gram| RC
|
||||||
|
P[Prune TF-IDF Cosine] -->|Low score| D
|
||||||
|
P -->|Keep| TL[timeline→system prompt]
|
||||||
|
end
|
||||||
|
subgraph D[② Document File Memory]
|
||||||
|
DS[DocStore JSON+TF-IDF]
|
||||||
|
Q1[Query auto-inject] -->|[Related Memory Docs]| SP
|
||||||
|
Q2[doc_query LLM active] -->|Consume+delete| DS
|
||||||
|
Q2 -->|Original timestamp write| RC
|
||||||
|
CD[FindColdDocs 72h] -->|docToTriples| G
|
||||||
|
end
|
||||||
|
subgraph G[③ Graph Database]
|
||||||
|
DB[(SQLite)]
|
||||||
|
IDX[Indexer BFS depth=2] -->|[Memory Index]| SP
|
||||||
|
MEM[memory_recall/commit]
|
||||||
|
SOC[person_query/set_trait]
|
||||||
|
end
|
||||||
|
subgraph H[④ Heartbeat Distillation]
|
||||||
|
REORG -->|Step3 Cold docs| CD
|
||||||
|
REORG -->|Step4 Bigram Jaccard| CONS[consolidation]
|
||||||
|
PIPE[Pipeline regex] -->|Name/Address/Likes/Age/Job| DB
|
||||||
|
end
|
||||||
|
SP[System Prompt] -->|Sequential assembly| LLM
|
||||||
|
LLM[LLM] -->|doc_query| Q2
|
||||||
|
LLM -->|memory_recall| MEM
|
||||||
|
```
|
||||||
|
|
||||||
|
See [`docs/en/ARCHITECTURE.md`](docs/en/ARCHITECTURE.md) for details.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build build-cli
|
||||||
|
./build/homed -data /tmp/ha
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interactive mode
|
||||||
|
./build/waiter
|
||||||
|
|
||||||
|
# Or single message
|
||||||
|
echo "Hello, remember that I like coffee" | ./build/waiter
|
||||||
|
```
|
||||||
|
|
||||||
|
API keys are configured via WebUI `http://localhost:8080` settings page, persisted in SQLite.
|
||||||
|
|
||||||
|
## Code Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
cmd/homed/ Daemon entry, assembles all subsystems
|
||||||
|
cmd/waiter/ CLI client (Unix socket)
|
||||||
|
internal/
|
||||||
|
├── agent/core/ Agent core: event loop, LLM tool loop, 7-stage pipeline
|
||||||
|
├── agent/api/ LLM Provider + 8 Lua adapters
|
||||||
|
├── memory/ Three-layer memory: Graph(SQLite) / Document(JSON+TF-IDF) / Text(JSONL)
|
||||||
|
├── knowledge/ Knowledge base (filesystem + TF-IDF)
|
||||||
|
├── plugin/ Plugin registry + .so/.dll dynamic loader
|
||||||
|
├── plugins/ 10 built-in plugins (webui/cli/timer/cmd/mcp/openclaw/agentcli/healthcheck/pluginmgr/files)
|
||||||
|
├── internal/sdk/ PluginSDK (Tool/Stage/Event three channels)
|
||||||
|
├── config/ SQLite config center
|
||||||
|
├── events/ Event bus
|
||||||
|
└── lua/adapters/ 8 LLM protocol adapter scripts
|
||||||
|
External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo, use `plugindev` toolchain, refer to Go and Lua examples in `example/`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
Core is functional, plugin system and SDK are ready. 10 built-in plugins. External plugin development via [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo using `plugindev` toolchain.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Project Overview](docs/en/OVERVIEW.md) | [中文](docs/zh/OVERVIEW.md)
|
||||||
|
- [Technical Architecture](docs/en/ARCHITECTURE.md) | [中文](docs/zh/ARCHITECTURE.md)
|
||||||
|
- [Plugin Development Guide](docs/en/PLUGIN_DEV.md) | [中文](docs/zh/PLUGIN_DEV.md)
|
||||||
|
- [Lua Adapter](docs/en/ADAPTER.md) | [中文](docs/zh/ADAPTER.md)
|
||||||
|
- [Knowledge Base Demo](knowledge/homeagent_architecture/content.md)
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build build-cli # Build daemon + CLI
|
||||||
|
make test # go test ./...
|
||||||
|
make install # Install to system
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependencies: Go 1.21+, CGo (go-sqlite3), Linux/Windows.
|
||||||
104
docs/en/ADAPTER.md
Normal file
104
docs/en/ADAPTER.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
**中文** | [English](../zh/ADAPTER.md)
|
||||||
|
|
||||||
|
# Lua Adapter — LLM Source Adaptation Guide
|
||||||
|
|
||||||
|
Each LLM API source corresponds to a Lua script, responsible for request transformation (Go unified format → API format) and response transformation (API format → Go unified format).
|
||||||
|
|
||||||
|
## Adapter Contract
|
||||||
|
|
||||||
|
The Lua script must return a table containing the following fields and functions:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local adapter = {}
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
adapter.name = "my_provider" -- Unique identifier, matches adapter field in config
|
||||||
|
adapter.version = "2.0.0"
|
||||||
|
adapter.endpoint = "/v1/chat/completions" -- API path, appended to base_url
|
||||||
|
adapter.headers = {} -- Additional HTTP request headers
|
||||||
|
|
||||||
|
-- Request transformation: Go → API
|
||||||
|
function adapter.transform_request(raw_json)
|
||||||
|
-- raw_json: Go's CompletionRequest JSON string
|
||||||
|
-- Returns: JSON string to send to API
|
||||||
|
return transformed_json
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Response transformation: API → Go
|
||||||
|
function adapter.transform_response(raw_json)
|
||||||
|
-- raw_json: API's raw response JSON string
|
||||||
|
-- Returns: Unified CompletionResponse JSON string
|
||||||
|
-- Unified format:
|
||||||
|
-- { content: "", finish_reason: "", token_usage: { prompt: N, completion: N, total: N }, tool_calls?: [...] }
|
||||||
|
return unified_json
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Stream chunk transformation (optional)
|
||||||
|
function adapter.transform_stream_chunk(raw_line)
|
||||||
|
-- raw_line: JSON string after data: in SSE
|
||||||
|
-- Returns: JSON of { content: "", done: bool }, return "" to skip this chunk
|
||||||
|
return chunk_json
|
||||||
|
end
|
||||||
|
|
||||||
|
return adapter
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unified CompletionRequest Format (Go → Adapter)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "deepseek-v4-flash",
|
||||||
|
"messages": [
|
||||||
|
{ "role": "system", "content": "..." },
|
||||||
|
{ "role": "user", "content": "..." },
|
||||||
|
{ "role": "assistant", "content": "...", "tool_calls": [...] }
|
||||||
|
],
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"stream": false,
|
||||||
|
"tools": [...],
|
||||||
|
"tool_choice": "auto"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unified CompletionResponse Format (Adapter → Go)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"content": "Response content",
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"token_usage": { "prompt": 10, "completion": 20, "total": 30 },
|
||||||
|
"tool_calls": [
|
||||||
|
{ "id": "call_xxx", "type": "function", "name": "tool_name", "arguments": { "key": "val" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lua VM Built-in Functions
|
||||||
|
|
||||||
|
`json.encode(table)` — Encode Lua table to JSON string
|
||||||
|
|
||||||
|
`json.decode(string)` — Decode JSON string to Lua table
|
||||||
|
|
||||||
|
`log(level, message)` — Output log (level: info/warn/error)
|
||||||
|
|
||||||
|
## Adapting Typical APIs
|
||||||
|
|
||||||
|
| API | endpoint | auth method | Format differences |
|
||||||
|
|-----|----------|-------------|-------------------|
|
||||||
|
| **OpenAI** | `/chat/completions` | `Authorization: Bearer <key>` | Standard OpenAI format |
|
||||||
|
| **DeepSeek** | `/chat/completions` | `Authorization: Bearer <key>` | OpenAI compatible, forces temperature=0 |
|
||||||
|
| **Anthropic** | `/v1/messages` | `x-api-key: <key>` | Messages API, system message separated, content as block array |
|
||||||
|
| **Gemini** | `/v1/models/{model}:generateContent` | `?key=<key>` or Bearer | contents/parts format, role uses model instead of assistant |
|
||||||
|
| **Mistral** | `/v1/chat/completions` | `Authorization: Bearer <key>` | OpenAI compatible |
|
||||||
|
| **Groq** | `/openai/v1/chat/completions` | `Authorization: Bearer <key>` | OpenAI compatible |
|
||||||
|
| **GitHub Models** | `/chat/completions` | `Authorization: Bearer <pat>` | OpenAI compatible |
|
||||||
|
| **Ollama** | `/api/chat` | None | Different options format |
|
||||||
|
|
||||||
|
## Steps to Add a New Source
|
||||||
|
|
||||||
|
1. Create `<name>.lua` under `internal/lua/adapters/`
|
||||||
|
2. Script defines `transform_request` and `transform_response`
|
||||||
|
3. (Optional) Define `transform_stream_chunk` for streaming support
|
||||||
|
4. Build verification: `go build ./cmd/homed/`
|
||||||
|
5. Test verification: `go test ./...`
|
||||||
347
docs/en/ARCHITECTURE.md
Normal file
347
docs/en/ARCHITECTURE.md
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
**中文** | [English](../zh/ARCHITECTURE.md)
|
||||||
|
|
||||||
|
# HomeAgent Architecture
|
||||||
|
|
||||||
|
The kernel performs zero IO; all external interaction comes from plugins.
|
||||||
|
|
||||||
|
## Message Processing Flow
|
||||||
|
|
||||||
|
### Full Pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
External input (via plugin InjectInput)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
eventLoop() → processTextInput()
|
||||||
|
│
|
||||||
|
├── on_input stage Plugins can intercept/rewrite/short-circuit
|
||||||
|
├── Context.Append Record to context window
|
||||||
|
├── Context.Prune Low-relevance events archived to Document
|
||||||
|
├── buildMemoryContext() Indexer recall → GraphDB BFS traversal
|
||||||
|
│
|
||||||
|
├── pre_action stage Plugins can inject system messages
|
||||||
|
│
|
||||||
|
├── [Tool Loop] process()
|
||||||
|
│ ├── buildSystemPrompt Persona + Memory + Knowledge + Context
|
||||||
|
│ ├── buildToolDefs Built-in tools + Plugin tools
|
||||||
|
│ ├── provider.Chat() LLM call
|
||||||
|
│ ├── post_action stage Plugins see LLM output + tool list
|
||||||
|
│ ├── Has tools?
|
||||||
|
│ │ ├── before_toolcall Plugins can reject/modify params
|
||||||
|
│ │ ├── executeToolCall Route to plugin/built-in
|
||||||
|
│ │ ├── after_toolcall Plugins can modify results
|
||||||
|
│ │ └── → back to post_action
|
||||||
|
│ └── No tools → exit loop
|
||||||
|
│
|
||||||
|
├── Context.Append(response)
|
||||||
|
├── before_output stage Plugins can modify final text
|
||||||
|
├── emitResponse() Send via output_send
|
||||||
|
└── after_output stage Read-only, cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
Code: `internal/agent/core/agent.go` — `process()` is the main tool loop
|
||||||
|
|
||||||
|
### 7 Stage Hooks
|
||||||
|
|
||||||
|
| Stage | Trigger | Plugin Capabilities |
|
||||||
|
|-------|---------|---------------------|
|
||||||
|
| `on_input` | Message arrives at Agent, zero processing | Blacklist/rate-limit/short-circuit reply |
|
||||||
|
| `pre_action` | Context ready, before LLM call | Inject external data into context |
|
||||||
|
| `post_action` | LLM returns text + tool list | Sensitive word filter/forced redirect |
|
||||||
|
| `before_toolcall` | Before single tool execution | Audit/reject/modify params |
|
||||||
|
| `after_toolcall` | After single tool execution | Desensitize/sort results |
|
||||||
|
| `before_output` | Final text ready, before sending | Format adaptation |
|
||||||
|
| `after_output` | Already sent | Statistics/logging |
|
||||||
|
|
||||||
|
Code: `internal/agent/core/stages.go` — `StageHost` orchestration
|
||||||
|
|
||||||
|
### Loop Rules
|
||||||
|
|
||||||
|
`post_action → [before_toolcall → execute → after_toolcall] → post_action` forms the inner loop.
|
||||||
|
Exit conditions: LLM has no tool calls / all rejected / exceeded limit.
|
||||||
|
|
||||||
|
### Short-Circuit Rules
|
||||||
|
|
||||||
|
Setting `ctx.Response` at any stage jumps to `after_output`.
|
||||||
|
|
||||||
|
## Three-Layer Memory
|
||||||
|
|
||||||
|
### Memory Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
① Context (Working Window)
|
||||||
|
RelevanceContext — In-memory events[] + JSON persistence
|
||||||
|
Append: Each input, Vectorize(char 1-2gram TF-IDF)
|
||||||
|
Prune: TF-IDF CosineSimilarity, keep topK + last 10
|
||||||
|
├── Keep → timeline → system prompt (sorted by time)
|
||||||
|
└── Low score → Document layer archive (ContextToDoc)
|
||||||
|
Save: 5s debounce write to disk
|
||||||
|
|
||||||
|
↓ Prune archive ↑ LLM active recall
|
||||||
|
|
||||||
|
② Document (File Memory)
|
||||||
|
DocStore — JSON files + TF-IDF InvertedIndex
|
||||||
|
Write: Prune archive / doc_commit / Graph snapshot (syncGraphToDocs)
|
||||||
|
Read:
|
||||||
|
├── Auto-inject: Query(input, top3) → [Related Memory Docs] → system prompt (read-only, update AccessCount)
|
||||||
|
└── LLM active: doc_query → Consume(read and delete)
|
||||||
|
→ context.Append{Timestamp: d.CreatedAt, Source: "cold_storage"} per doc
|
||||||
|
→ Docs written to context timeline with original timestamps, deleted from docStore
|
||||||
|
Cold: FindColdDocs(72h, ≤2 accesses) → docToTriples → Graph
|
||||||
|
|
||||||
|
↓ Cold doc distillation ↑ Auto recall
|
||||||
|
|
||||||
|
③ Graph (Graph Database)
|
||||||
|
SQLite — entities + relations tables
|
||||||
|
Write: memory_commit / cold doc distillation / Pipeline rule distillation
|
||||||
|
Read:
|
||||||
|
├── Auto recall: Indexer.BuildContext(input)
|
||||||
|
│ → TF-IDF entity name search → BFS depth=2
|
||||||
|
│ → [Memory Index] → system prompt
|
||||||
|
└── LLM active: memory_recall / doc_query
|
||||||
|
Social: person_query / set_trait / relate (wraps GraphDB)
|
||||||
|
|
||||||
|
④ Distillation Pipeline (30min heartbeat)
|
||||||
|
distillContext → window > 2×maxSize → force Prune
|
||||||
|
syncGraphToDocs → Graph snapshot to Document (cross-layer searchable)
|
||||||
|
reorgGraph:
|
||||||
|
Step1: indexer.Sync — rebuild entity TF-IDF vector index
|
||||||
|
Step2: docStore.Reindex — rebuild document TF-IDF vector index
|
||||||
|
Step3: Cold docs → docToTriples → GraphDB.Commit
|
||||||
|
Step4: Entity similarity (Bigram Jaccard > 0.75) → consolidation → LLM decides merge
|
||||||
|
Step5: evaluateGraphQuality → LLM decides keep/delete
|
||||||
|
|
||||||
|
⑤ Pipeline Rule Distiller (every heartbeat)
|
||||||
|
distillOnce → regex match personal info:
|
||||||
|
我叫X / 我住在X / 我喜欢X / 我X岁 / 我的工作是X
|
||||||
|
→ triples → GraphDB.Commit
|
||||||
|
```
|
||||||
|
|
||||||
|
### TF-IDF Vectorization (char 1-2 gram)
|
||||||
|
|
||||||
|
TF-IDF is the core algorithm running through all three memory layers, used in 4 independent locations:
|
||||||
|
|
||||||
|
| Location | File | Purpose | Algorithm |
|
||||||
|
|----------|------|---------|-----------|
|
||||||
|
| Context Prune | `context.go:162` | Trim low-relevance context events | CosineSimilarity(queryVec, evt.Vector) |
|
||||||
|
| DocStore Query | `document.go:205` | Recall related content from document memory | InvertedIndex + CosineSimilarity |
|
||||||
|
| Indexer Entity Search | `indexer.go:149` | Recall related entities from Graph | InvertedIndex + CosineSimilarity |
|
||||||
|
| Entity Similarity Detection | `agent.go:2297` | Detect similar entities in Graph | Bigram Jaccard (>0.75 → consolidation) |
|
||||||
|
|
||||||
|
### Context Layer
|
||||||
|
|
||||||
|
`internal/agent/core/context.go` — `RelevanceContext`
|
||||||
|
- Maintains recent event list, writes JSON on each Append/Prune to prevent data loss
|
||||||
|
- TF-IDF relevance scoring on user input, keeps topK
|
||||||
|
|
||||||
|
### Document Layer
|
||||||
|
|
||||||
|
`internal/memory/document/doc.go` — `Store`
|
||||||
|
- Consume-on-read mode: deleted after `doc_query` retrieval
|
||||||
|
- TF-IDF index with character bigram + inverted index
|
||||||
|
|
||||||
|
### Graph Layer
|
||||||
|
|
||||||
|
`internal/memory/graph.go` — `GraphDB`
|
||||||
|
- SQLite WAL mode, two tables
|
||||||
|
- `Commit(triples)` — UPSERT entities + INSERT relations
|
||||||
|
- `Recall(keywords, depth)` — Keyword LIKE search + BFS traversal
|
||||||
|
|
||||||
|
### Memory Tools (LLM-callable)
|
||||||
|
|
||||||
|
| Tool | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `memory_recall` | Recall from Graph |
|
||||||
|
| `memory_commit` | Write triples to Graph |
|
||||||
|
| `memory_introspect` | View memory statistics |
|
||||||
|
| `doc_query` | Search from Document |
|
||||||
|
| `doc_commit` | Write to Document |
|
||||||
|
|
||||||
|
### Other Memory Layers
|
||||||
|
|
||||||
|
- **Social** (`internal/memory/social/social.go`) — Persona traits and relationship network, wraps GraphDB entity types
|
||||||
|
- **Text Memory** (`internal/memory/text/text.go`) — Raw conversation JSONL logs, rotation strategy
|
||||||
|
- **Memory Indexer** (`internal/memory/indexer.go`) — Entity vectorization, auto-inject into system prompt
|
||||||
|
|
||||||
|
### Distillation Pipeline
|
||||||
|
|
||||||
|
`internal/memory/pipeline/pipeline.go`
|
||||||
|
- 10-minute tick, 7-day retention
|
||||||
|
- Rule-based triple extraction (name / location / likes / age / job patterns)
|
||||||
|
- Writes to GraphDB
|
||||||
|
|
||||||
|
### Context Pruning
|
||||||
|
|
||||||
|
```
|
||||||
|
Heartbeat 30min:
|
||||||
|
├── distillContext() — Distill current context
|
||||||
|
├── syncGraphToDocs() — Graph → Document sync
|
||||||
|
└── reorgGraph()
|
||||||
|
├── Indexer.Sync()
|
||||||
|
├── DocStore.Reindex()
|
||||||
|
├── Cold docs → Graph
|
||||||
|
└── Entity conflicts → enqueueConsolidationTask()
|
||||||
|
│
|
||||||
|
selfInputCh → LLM decides merge/skip
|
||||||
|
```
|
||||||
|
|
||||||
|
Entity conflict detection heuristic (bigram Jaccard > 0.5), routed through `selfInputCh` internal channel, LLM makes the final merge decision.
|
||||||
|
|
||||||
|
## Knowledge Base
|
||||||
|
|
||||||
|
`internal/knowledge/knowledge.go`
|
||||||
|
- File directory `knowledge/<name>/content.md`
|
||||||
|
- Independent TF-IDF index, separate from memory system
|
||||||
|
- `knowledge_search` / `knowledge_create` / `knowledge_list`
|
||||||
|
|
||||||
|
## Provider & Lua Adapter Layer
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Provider Interface (Name / Chat / ChatStream)
|
||||||
|
│
|
||||||
|
├── OpenAIProvider — Standard OpenAI API
|
||||||
|
├── OllamaProvider — Local Ollama
|
||||||
|
└── LuaAdaptedProvider (primary)
|
||||||
|
├── Serialize CompletionRequest → JSON
|
||||||
|
├── adapter.transform_request() → API format
|
||||||
|
├── HTTP request + adapter.headers
|
||||||
|
├── adapter.transform_response() → unified format
|
||||||
|
└── Deserialize
|
||||||
|
```
|
||||||
|
|
||||||
|
Code: `internal/agent/api/provider.go`
|
||||||
|
|
||||||
|
ProviderManager manages multiple sources, fallback in registration order. Lua adapters at `internal/lua/adapters/`, each `.lua` script defines `transform_request` / `transform_response` / `transform_stream_chunk`.
|
||||||
|
|
||||||
|
VM built-ins: `json.encode` / `json.decode` / `log` / `http_get` / `http_post`.
|
||||||
|
|
||||||
|
## Plugin System
|
||||||
|
|
||||||
|
### Three Loading Methods
|
||||||
|
|
||||||
|
| Method | Registration Mechanism | Compilation | Usage |
|
||||||
|
|--------|----------------------|-------------|-------|
|
||||||
|
| Built-in | `init()` → `RegisterFactory` | `internal/plugins/` compiled into kernel | webui/cli/timer/mcp etc. |
|
||||||
|
| External `.so`/`.dll` | `plugin.Open` dynamic loading | `-buildmode=plugin` | qq/files/web/memo etc. |
|
||||||
|
| Lua script plugin | Parse `main.lua` to register tools | No compilation, hot-reload | luaplugintest/testlua etc. |
|
||||||
|
| SKILL plugin | Parse `SKILL.md` | Markdown definition | OpenClaw compatible |
|
||||||
|
|
||||||
|
Built-in plugin registration: `internal/plugins/all.go` blank imports → each plugin `init()` → `Registry.Load()` scans directory to match factory.
|
||||||
|
External plugin loading: `internal/plugin/dynamic.go` → copy to SHA256 temp path (bypass `plugin.Open` path cache) → `Open` + `Lookup("NewPlugin")`.
|
||||||
|
Lua script plugin loading: `internal/lua/` → parse `main.lua` via Lua VM, call `start()` to register tools.
|
||||||
|
|
||||||
|
### PluginSDK Three Channels
|
||||||
|
|
||||||
|
```
|
||||||
|
Plugin ──→ Kernel
|
||||||
|
|
||||||
|
RegisterTool(name, fn) ──→ buildToolDefs() / executeToolCall()
|
||||||
|
RegisterStage(stage, fn) ──→ runStage() called at corresponding phase
|
||||||
|
Subscribe(event, fn) ──→ Publish() notify all subscribers
|
||||||
|
```
|
||||||
|
|
||||||
|
`internal/sdk/` bridges external SDK interface to kernel, defines complete PluginSDK:
|
||||||
|
|
||||||
|
```go
|
||||||
|
sdk.RegisterTool(name, def, handler)
|
||||||
|
sdk.RegisterStage(stage, handler)
|
||||||
|
sdk.Publish(event)
|
||||||
|
sdk.InjectInput(source, channel, payload)
|
||||||
|
sdk.InjectInterrupt(source, channel, payload)
|
||||||
|
sdk.Memory().Recall/Commit
|
||||||
|
sdk.Knowledge().Search/Create
|
||||||
|
sdk.Settings().Get/Set/List
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin Interface
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Plugin interface {
|
||||||
|
Name() string
|
||||||
|
Start(sdk *PluginSDK) error
|
||||||
|
Stop() error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interrupt Mechanism
|
||||||
|
|
||||||
|
```
|
||||||
|
interceptLoop (goroutine)
|
||||||
|
├── InputInterruptChan() ← Timer/message notifications
|
||||||
|
├── (a) cancelLLM() → Cancel Provider HTTP request
|
||||||
|
├── (b) interceptCh → process() pre-loop read [interrupt message]
|
||||||
|
└── (c) InjectInput() → Trigger new processing when idle
|
||||||
|
```
|
||||||
|
|
||||||
|
Three delivery paths:
|
||||||
|
|
||||||
|
| Path | Effect | Timing |
|
||||||
|
|------|--------|--------|
|
||||||
|
| cancelLLM | Cancel current HTTP request | On context.Canceled |
|
||||||
|
| interceptCh | Insert `[interrupt message]` in process() | Before each LLM call |
|
||||||
|
| InjectInput | Trigger new processing when eventLoop is idle | No ongoing request |
|
||||||
|
|
||||||
|
Code: `internal/agent/core/agent.go` — `interceptLoop` / `drainInterrupt`
|
||||||
|
|
||||||
|
## Configuration System
|
||||||
|
|
||||||
|
`internal/config/registry.go` — ConfigRegistry
|
||||||
|
|
||||||
|
- SQLite storage, `config` table + `config_<plugin>` independent tables
|
||||||
|
- Namespaces: `core.*` / `plugin.<name>.*`
|
||||||
|
- `RegisterDefault` inserts ~80 default keys (seeds for 8 LLM sources)
|
||||||
|
- WebUI settings page `/api/v1/settings` for read/write
|
||||||
|
|
||||||
|
## Code Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
cmd/homed/main.go — Entry: assembles all subsystems
|
||||||
|
cmd/waiter/main.go — CLI client (Unix socket)
|
||||||
|
internal/
|
||||||
|
├── agent/
|
||||||
|
│ ├── core/ — Agent core (eventLoop/process/stages/context)
|
||||||
|
│ ├── api/ — Provider interface + LuaAdaptedProvider
|
||||||
|
│ ├── io/ — IOManager (queue/interrupt/output)
|
||||||
|
│ └── personal.go — Persona loading
|
||||||
|
├── plugin/
|
||||||
|
│ ├── registry.go — Registry + lifecycle
|
||||||
|
│ ├── dynamic.go — .so dynamic loader
|
||||||
|
│ └── manifest.go — plugin.json metadata
|
||||||
|
├── plugins/ — Built-in plugin implementations
|
||||||
|
│ ├── all.go — Blank imports
|
||||||
|
│ ├── webui/ — HTTP server + embedded SPA
|
||||||
|
│ ├── cli/ — Unix socket CLI
|
||||||
|
│ ├── timer/ — Timer
|
||||||
|
│ ├── cmd/ — Command execution
|
||||||
|
│ ├── mcp/ — MCP protocol
|
||||||
|
│ ├── openclaw/ — OpenClaw compatible
|
||||||
|
│ ├── agentcli/ — PTY terminal
|
||||||
|
│ ├── healthcheck/ — Health check
|
||||||
|
│ └── pluginmgr/ — Plugin manager
|
||||||
|
├── internal/sdk/ — PluginSDK definitions
|
||||||
|
│ ├── plugin.go — Plugin interface + PluginSDK
|
||||||
|
│ ├── memory.go — MemoryAPI
|
||||||
|
│ ├── knowledge.go — KnowledgeAPI
|
||||||
|
│ ├── settings.go — SettingsAPI
|
||||||
|
│ └── llm.go — LLMAPI
|
||||||
|
├── memory/
|
||||||
|
│ ├── graph.go — SQLite graph database
|
||||||
|
│ ├── indexer.go — Graph → vector index
|
||||||
|
│ ├── vector/store.go — TF-IDF vector engine
|
||||||
|
│ ├── document/doc.go — Document memory
|
||||||
|
│ ├── text/text.go — Text logs
|
||||||
|
│ └── pipeline/ — Distiller
|
||||||
|
├── knowledge/knowledge.go — Knowledge base
|
||||||
|
├── lua/
|
||||||
|
│ ├── vm.go — Lua VM (json/log/http)
|
||||||
|
│ └── adapters/ — 8 LLM adapter scripts
|
||||||
|
├── config/registry.go — SQLite config center
|
||||||
|
├── events/bus.go — Event bus
|
||||||
|
├── tracker/ — OverlayFS change tracking
|
||||||
|
├── supervisor/ — Daemon management
|
||||||
|
├── snapshot/ — Snapshots
|
||||||
|
└── tokenizer/ — Chinese tokenization (jieba wrapper)
|
||||||
|
```
|
||||||
74
docs/en/OVERVIEW.md
Normal file
74
docs/en/OVERVIEW.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
**中文** | [English](../zh/OVERVIEW.md)
|
||||||
|
|
||||||
|
# HomeAgent — Project Overview
|
||||||
|
|
||||||
|
## What Is This
|
||||||
|
|
||||||
|
HomeAgent is a continuously-running personal intelligent Agent framework.
|
||||||
|
|
||||||
|
Core architecture: a long-running kernel process (`homed`) that connects to various IO channels (QQ, Web, CLI, etc.) through a plugin system. The kernel handles LLM orchestration, memory management, and knowledge retrieval; plugins handle all external IO — sending/receiving messages, file operations, web search, etc.
|
||||||
|
|
||||||
|
### Key Innovations
|
||||||
|
|
||||||
|
**Separation of Core Domain and Application Domain** — This is the first Agent framework to explicitly make this distinction. The kernel (core domain) performs zero IO; all IO capabilities belong to plugins (application domain). The boundary is clearly defined through PluginSDK:
|
||||||
|
- Plugins register tools (Tool) with the kernel for LLM invocation
|
||||||
|
- Plugins hook into the processing pipeline (Stage) to intercept/rewrite message flow at various phases
|
||||||
|
- Plugins subscribe/publish events (Event) for loosely-coupled communication
|
||||||
|
- Plugins queue or interrupt input delivery through IO API
|
||||||
|
|
||||||
|
The significance: the kernel stays pure (zero IO, only orchestration and memory), plugins stay flexible (each does its job, hot-loadable), with no cross-contamination.
|
||||||
|
|
||||||
|
**Three-Layer Memory Architecture** — Solves the memory decay problem for long-running agents:
|
||||||
|
- **Context Layer**: In-memory TF-IDF scored event window, maintains recent context in real-time, low-relevance events automatically sink to the next layer
|
||||||
|
- **Document Layer**: JSON files + TF-IDF vector-indexed temporary memory, supports explicit submission and implicit archival, cold data distills to Graph
|
||||||
|
- **Graph Layer**: SQLite graph database, persists entities and relations, BFS traversal recall, distillation pipeline extracts triples from conversations
|
||||||
|
|
||||||
|
Three progressive layers: context → cold archive → long-term graph memory, ensuring the agent doesn't degrade over time.
|
||||||
|
|
||||||
|
## What It Actually Does
|
||||||
|
|
||||||
|
Code is in the project root, implemented in Go.
|
||||||
|
|
||||||
|
**Kernel** (`internal/agent/core/agent.go`):
|
||||||
|
- Maintains a message loop (`eventLoop`), queuing input from the IO layer
|
||||||
|
- Each input goes through the full processing pipeline: memory recall → persona injection → LLM call → tool execution → output delivery
|
||||||
|
- LLM calls abstracted through Provider interface, supports 8 LLM sources with automatic fallback
|
||||||
|
- Context management (`context.go`) based on TF-IDF scoring, automatic pruning of low-relevance events
|
||||||
|
|
||||||
|
**Memory System** (`internal/memory/`):
|
||||||
|
- **GraphDB** (`graph.go`) — SQLite, entities + relations tables, BFS traversal
|
||||||
|
- **Document Store** (`document/doc.go`) — Temporary memory, JSON files + TF-IDF vector index, consume-on-read
|
||||||
|
- **Text Memory** (`text/text.go`) — Raw conversation logs, JSONL file rotation
|
||||||
|
- **Social Store** (`social/social.go`) — Persona traits + relationship network, wraps GraphDB
|
||||||
|
- **Memory Indexer** (`indexer.go`) — Auto-vectorizes GraphDB entities, recalls and injects into system prompt on user input
|
||||||
|
|
||||||
|
**Knowledge Base** (`internal/knowledge/knowledge.go`):
|
||||||
|
- File system directory `knowledge/<name>/content.md`
|
||||||
|
- TF-IDF vector search, independent index instance from the memory system
|
||||||
|
- LLM operates via three tools: `knowledge_search` / `knowledge_create` / `knowledge_list`
|
||||||
|
|
||||||
|
**Plugin System** (`internal/plugin/`):
|
||||||
|
- Built-in plugins: Go `init()` self-registration, compiled into kernel
|
||||||
|
- External plugins: Go `-buildmode=plugin` compiled to `.so`/`.dll`, dynamically loaded via `plugin.Open`; also supports Lua script plugins
|
||||||
|
- PluginSDK (`internal/sdk/`) defines three channels: RegisterTool / RegisterStage / Subscribe
|
||||||
|
- 7 stage hooks: on_input → pre_action → post_action → before_toolcall → after_toolcall → before_output → after_output
|
||||||
|
|
||||||
|
**LLM Provider** (`internal/agent/api/provider.go`):
|
||||||
|
- Provider interface: Name / Chat / ChatStream
|
||||||
|
- Three implementations: OpenAIProvider (standard OpenAI API), OllamaProvider (local), LuaAdaptedProvider (Lua adapter)
|
||||||
|
- LuaAdapter located at `internal/lua/adapters/`, each LLM source has a corresponding `.lua` script
|
||||||
|
- 8 built-in adapters: deepseek / openai / anthropic / gemini / mistral / groq / github / ollama
|
||||||
|
|
||||||
|
**WebUI** (`internal/plugins/webui/`):
|
||||||
|
- Embedded SPA dashboard (`dashboard.html` packaged via `//go:embed`)
|
||||||
|
- REST API: status query, configuration management, memory operations, knowledge management, plugin management
|
||||||
|
- OpenAI API-compatible `/v1/chat/completions` endpoint
|
||||||
|
- SSE event stream `/api/v1/chat/events`
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
Core functionality is operational. Plugin system and SDK are ready for independent external plugin development.
|
||||||
|
|
||||||
|
- Built-in plugins: webui / cli / timer / cmd / mcp / agentcli / healthcheck / pluginmgr / openclaw / files
|
||||||
|
- External plugin examples ([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo `example/`, both Go and Lua types): qq / files / web / memo / bili / editdoc / a2a / ocr / sanitizer / luaplugintest / testlua
|
||||||
|
- Distribution: `.hmap` plugin package format, installable via WebUI
|
||||||
477
docs/en/PLUGIN_DEV.md
Normal file
477
docs/en/PLUGIN_DEV.md
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
**中文** | [English](../zh/PLUGIN_DEV.md)
|
||||||
|
|
||||||
|
# HomeAgent Plugin Development Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
All external interaction capabilities of HomeAgent comes from plugins. Plugins interact with the kernel through `PluginSDK` (Go API).
|
||||||
|
|
||||||
|
**SDK Repository**: Plugin development tools, template code, and example plugins are hosted in the [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repository.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitcode.com/JianFeeeee/homeagent-sdk.git
|
||||||
|
cd homeagent-sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
Each plugin implements a three-method interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Plugin interface {
|
||||||
|
Name() string
|
||||||
|
Start(sdk *PluginSDK) error
|
||||||
|
Stop() error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Three Development Methods
|
||||||
|
|
||||||
|
| Method | Use Case | Complexity |
|
||||||
|
|--------|----------|------------|
|
||||||
|
| **Dynamic .so/.dll plugin (recommended)** | Independently distributed third-party plugins | Medium, generated using `plugindev` toolchain |
|
||||||
|
| **Built-in plugin** | Released with HomeAgent | Simple, requires merging into main repo |
|
||||||
|
| **Lua script plugin** | Lightweight rapid prototyping | Simple, generated using `plugindev init --lua` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Quick Start: Using the plugindev Toolchain
|
||||||
|
|
||||||
|
`plugindev` is the unified plugin development toolchain provided in the SDK repository, supporting both Go and Lua plugin types.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd homeagent-sdk/tools/plugindev
|
||||||
|
go build -o plugindev.exe
|
||||||
|
# Add plugindev.exe to PATH or use directly
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a Go Plugin
|
||||||
|
|
||||||
|
```bash
|
||||||
|
plugindev init myplugin
|
||||||
|
cd myplugin
|
||||||
|
# Edit plugin code
|
||||||
|
vim plugin.go
|
||||||
|
# Build and package
|
||||||
|
plugindev build
|
||||||
|
# Output: dist/myplugin_linux_amd64.hmap (or windows_amd64)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a Lua Plugin
|
||||||
|
|
||||||
|
```bash
|
||||||
|
plugindev init myluaplugin --lua
|
||||||
|
cd myluaplugin
|
||||||
|
# Edit plugin code
|
||||||
|
vim main.lua
|
||||||
|
# Local test
|
||||||
|
lua main.lua
|
||||||
|
# Build and package
|
||||||
|
plugindev build
|
||||||
|
# Output: dist/myluaplugin_lua.hmap
|
||||||
|
```
|
||||||
|
|
||||||
|
### Template Project Structure
|
||||||
|
|
||||||
|
**Go plugin**:
|
||||||
|
|
||||||
|
```
|
||||||
|
myplugin/
|
||||||
|
├── plg.json — Plugin metadata (name, version, entry, target platform)
|
||||||
|
├── main.go — Entry point (compiled for non-Windows or non-cgo)
|
||||||
|
├── plugin.go — Plugin implementation (Plugin interface)
|
||||||
|
├── go.mod — Go module definition
|
||||||
|
└── README.md — Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lua plugin**:
|
||||||
|
|
||||||
|
```
|
||||||
|
myluaplugin/
|
||||||
|
├── plg.json — Plugin metadata (entry: "main.lua", targets: "lua")
|
||||||
|
├── main.lua — Plugin implementation (Lua version of Plugin interface)
|
||||||
|
├── sdk.lua — SDK mock layer (supports `lua main.lua` standalone testing)
|
||||||
|
└── README.md — Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build & Package
|
||||||
|
|
||||||
|
`plugindev build` automatically handles compilation and packaging:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd myplugin
|
||||||
|
plugindev build
|
||||||
|
```
|
||||||
|
|
||||||
|
Execution process:
|
||||||
|
1. Reads `plg.json` to determine target platform
|
||||||
|
2. **Go plugin**: Runs `go build -buildmode=plugin` (Linux) or `-buildmode=c-shared` (Windows)
|
||||||
|
3. **Lua plugin**: Packages source code directly, no compilation needed
|
||||||
|
4. Generates `plugin.json` manifest file
|
||||||
|
5. Packages as `.hmap` distribution (zip format, containing `plugin.json` + `plugin.so`/`plugin.dll`/`main.lua`)
|
||||||
|
|
||||||
|
Output in `dist/` directory:
|
||||||
|
```
|
||||||
|
dist/
|
||||||
|
├── myplugin_linux_amd64.hmap # Go plugin Linux version
|
||||||
|
├── myplugin_windows_amd64.hmap # Go plugin Windows version
|
||||||
|
└── myplugin_lua.hmap # Lua plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
Install via PluginMgr HTTP API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Kernel PluginMgr listens on :9876
|
||||||
|
curl -X POST http://127.0.0.1:9876/plugins \
|
||||||
|
-F "file=@dist/myplugin_linux_amd64.hmap"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or upload via WebUI plugin management page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Go Plugin Development in Detail
|
||||||
|
|
||||||
|
### Plugin Interface
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||||
|
|
||||||
|
type Plugin struct {
|
||||||
|
name string
|
||||||
|
sdk *sdk.PluginSDK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) Name() string { return p.name }
|
||||||
|
|
||||||
|
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||||
|
p.sdk = s
|
||||||
|
// Register config items, tools, stage hooks, etc.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) Stop() error {
|
||||||
|
// Clean up resources
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPluginFactory creates plugin instance (called by main.go or Windows bridge)
|
||||||
|
func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
||||||
|
return &Plugin{name: name}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entry Point
|
||||||
|
|
||||||
|
`main.go` provides the `NewPlugin` export function, which is the entry point when the kernel loads the plugin:
|
||||||
|
|
||||||
|
```go
|
||||||
|
//go:build !windows || !cgo
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||||
|
|
||||||
|
func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
||||||
|
return NewPluginFactory(name, config)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For Windows `-buildmode=c-shared`, `plugindev build` auto-generates C ABI bridge code, no manual handling needed.
|
||||||
|
|
||||||
|
### PluginSDK Core API
|
||||||
|
|
||||||
|
#### Tool Registration — Make your capabilities callable by LLM
|
||||||
|
|
||||||
|
```go
|
||||||
|
s.RegisterTool("weather_query", sdk.ToolDef{
|
||||||
|
Name: "weather_query",
|
||||||
|
Description: "Query weather for a specified city",
|
||||||
|
Parameters: map[string]interface{}{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]interface{}{
|
||||||
|
"city": map[string]interface{}{
|
||||||
|
"type": "string",
|
||||||
|
"description": "City name, e.g. Beijing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": []string{"city"},
|
||||||
|
},
|
||||||
|
}, func(args map[string]interface{}) (interface{}, error) {
|
||||||
|
city, _ := args["city"].(string)
|
||||||
|
return map[string]interface{}{
|
||||||
|
"city": city,
|
||||||
|
"temp": 25,
|
||||||
|
"weather": "Sunny",
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Stage Hooks — Intervene in message processing flow
|
||||||
|
|
||||||
|
7 stages:
|
||||||
|
|
||||||
|
| Stage | Timing | Purpose |
|
||||||
|
|-------|--------|---------|
|
||||||
|
| `on_input` | Message just arrived at Agent | Blacklist, rate-limit, short-circuit |
|
||||||
|
| `pre_action` | About to call LLM | Inject context |
|
||||||
|
| `post_action` | LLM returned results | Modify output/tool list |
|
||||||
|
| `before_toolcall` | Before tool execution | Audit, reject, modify params |
|
||||||
|
| `after_toolcall` | After tool execution | Desensitize, rewrite results |
|
||||||
|
| `before_output` | Before output | Format adaptation |
|
||||||
|
| `after_output` | After output | Statistics/logging |
|
||||||
|
|
||||||
|
```go
|
||||||
|
s.RegisterStage(sdk.StagePreAction, func(ctx *sdk.StageContext) error {
|
||||||
|
ctx.Lock()
|
||||||
|
ctx.ContextMsgs = append(ctx.ContextMsgs, map[string]interface{}{
|
||||||
|
"role": "system",
|
||||||
|
"content": "Injected context content",
|
||||||
|
})
|
||||||
|
ctx.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Configuration Management
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Register config definition
|
||||||
|
s.Settings().RegisterDef(sdk.ConfigDef{
|
||||||
|
Key: "plugin.myplugin.api_key",
|
||||||
|
Default: "",
|
||||||
|
Type: "string",
|
||||||
|
DisplayName: "API Key",
|
||||||
|
Description: "API key",
|
||||||
|
Category: "myplugin",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Read/write config
|
||||||
|
val, err := s.Settings().Get("api_key")
|
||||||
|
s.Settings().Set("api_key", "new-value")
|
||||||
|
|
||||||
|
// Read core config
|
||||||
|
s.Settings().GetCore("llm.model")
|
||||||
|
|
||||||
|
// Read other plugin's config
|
||||||
|
s.Settings().GetPlugin("other_plugin", "some_key")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Input Delivery
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Queued delivery (processed in order)
|
||||||
|
s.InjectInput(source, channel string, payload map[string]interface{})
|
||||||
|
|
||||||
|
// Interrupt delivery (can interrupt current LLM processing)
|
||||||
|
s.InjectInterrupt(source, channel string, payload map[string]interface{})
|
||||||
|
|
||||||
|
// Shortcuts
|
||||||
|
s.InjectText(source, channel, text string)
|
||||||
|
s.InjectInterruptText(source, channel, text string)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Event Subscription
|
||||||
|
|
||||||
|
```go
|
||||||
|
unsub := s.Subscribe("tool_call", func(evt *events.Event) {
|
||||||
|
log.Printf("Tool was called: %v", evt.Payload)
|
||||||
|
})
|
||||||
|
defer unsub()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Capability Access
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Memory
|
||||||
|
s.Memory().Recall(query string) ([]MemItem, error)
|
||||||
|
s.Memory().Commit(triples []Triple) error
|
||||||
|
|
||||||
|
// Knowledge
|
||||||
|
s.Knowledge().Search(query string) ([]string, error)
|
||||||
|
|
||||||
|
// LLM source management
|
||||||
|
s.LLM().ListSources() []SourceInfo
|
||||||
|
s.LLM().SetSource(name string) error
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Lua Plugin Development in Detail
|
||||||
|
|
||||||
|
Lua plugins are suitable for lightweight rapid prototyping, requiring no Go compilation environment. Changes take effect after kernel restart.
|
||||||
|
|
||||||
|
### Plugin Structure
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- main.lua
|
||||||
|
local plugin = {
|
||||||
|
name = "myluaplugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
function plugin.start(sdk)
|
||||||
|
sdk.log("info", "myluaplugin starting...")
|
||||||
|
|
||||||
|
sdk.register_tool("myluaplugin_hello", {
|
||||||
|
description = "A hello world tool",
|
||||||
|
parameters = {
|
||||||
|
type = "object",
|
||||||
|
properties = {}
|
||||||
|
}
|
||||||
|
}, function(args)
|
||||||
|
return { content = "Hello from myluaplugin plugin!" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
sdk.log("info", "myluaplugin started")
|
||||||
|
end
|
||||||
|
|
||||||
|
function plugin.stop()
|
||||||
|
sdk.log("info", "myluaplugin stopped")
|
||||||
|
end
|
||||||
|
|
||||||
|
return plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### SDK Mock Layer
|
||||||
|
|
||||||
|
`sdk.lua` provides a pure Lua SDK mock implementation, supporting `lua main.lua` standalone testing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lua main.lua
|
||||||
|
# Output:
|
||||||
|
# [lua-plugin] info: myluaplugin starting...
|
||||||
|
# [lua-plugin] register_tool: myluaplugin_hello
|
||||||
|
# [lua-plugin] info: myluaplugin started
|
||||||
|
```
|
||||||
|
|
||||||
|
When running inside the kernel, `sdk.*` global variables are injected by the Go layer, and all functions marked with `-- !impl` are replaced with real implementations.
|
||||||
|
|
||||||
|
### Lua SDK API
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `sdk.log(level, msg)` | Log output |
|
||||||
|
| `sdk.register_tool(name, def, handler)` | Register tool |
|
||||||
|
| `sdk.register_stage(stage, handler)` | Register stage hook |
|
||||||
|
| `sdk.register_api(name)` | Register API |
|
||||||
|
| `sdk.get_setting(key)` | Read config |
|
||||||
|
| `sdk.set_setting(key, value)` | Write config |
|
||||||
|
| `sdk.inject_text(source, channel, text)` | Deliver text message |
|
||||||
|
| `sdk.inject_interrupt(source, channel, text)` | Interrupt delivery |
|
||||||
|
| `sdk.json.encode(val)` | JSON encode |
|
||||||
|
| `sdk.json.decode(str)` | JSON decode |
|
||||||
|
| `sdk.http.get(url)` | HTTP GET request (`-- !impl`) |
|
||||||
|
| `sdk.http.post(url, body, content_type)` | HTTP POST request (`-- !impl`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Built-in Plugins
|
||||||
|
|
||||||
|
Built-in plugins use `init()` self-registration, compiled into the kernel, no separate deployment needed.
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
internal/plugins/yourplugin/
|
||||||
|
plugin.go — Plugin main file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Minimal Plugin Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
package yourplugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
|
||||||
|
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
plugin.RegisterFactory("yourplugin", func(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
||||||
|
return New(name), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(name string) *Plugin {
|
||||||
|
return &Plugin{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) Name() string { return p.name }
|
||||||
|
|
||||||
|
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||||
|
// Initialize plugin here: start goroutines, register tools, subscribe events, etc.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) Stop() error {
|
||||||
|
// Clean up resources
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register with Kernel
|
||||||
|
|
||||||
|
Add blank import in `internal/plugins/all.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/yourplugin"
|
||||||
|
// ... other plugins
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Best Practices
|
||||||
|
|
||||||
|
1. `Start()` is non-blocking — start long tasks in goroutines, don't block Start
|
||||||
|
2. `Stop()` cleans up resources — close connections, stop goroutines, cancel subscriptions
|
||||||
|
3. Unique tool names — use plugin name prefix to avoid conflicts
|
||||||
|
4. When handler returns `error`, LLM will receive it and may retry
|
||||||
|
5. Use `InjectInterruptText` for interrupts, `InjectText` for normal delivery
|
||||||
|
6. Use `Settings().Get/Set` for config, don't hardcode
|
||||||
|
7. External Go plugins compile independently, not tied to kernel version; only built-in plugins need recompilation with kernel
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Example Plugin Reference
|
||||||
|
|
||||||
|
### SDK Repository Examples (`homeagent-sdk/example/`)
|
||||||
|
|
||||||
|
| Example | Type | Features |
|
||||||
|
|---------|------|----------|
|
||||||
|
| [memo](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/memo) | Go | Memo management, PreAction injection + timed interrupt dual reminder |
|
||||||
|
| [files](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/files) | Go | File system operations, 4 write modes, sandbox isolation |
|
||||||
|
| [web](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/web) | Go | DuckDuckGo search + web scraping, SSRF protection |
|
||||||
|
| [qq](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/qq) | Go | NapCat OneBot integration, 17 tools |
|
||||||
|
| [bili](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/bili) | Go | Bilibili video download (you-get) |
|
||||||
|
| [editdoc](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/editdoc) | Go | Office document editing and format conversion |
|
||||||
|
| [a2a](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/a2a) | Go | Agent-to-Agent protocol |
|
||||||
|
| [ocr](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/ocr) | Go | Offline text recognition (Tesseract) |
|
||||||
|
| [sanitizer](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/sanitizer) | Go | Output sanitizer filter |
|
||||||
|
| [luaplugintest](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/luaplugintest) | Lua | Lua plugin Hello World |
|
||||||
|
| [testlua](https://gitcode.com/JianFeeeee/homeagent-sdk/tree/main/example/testlua) | Lua | Lua plugin example |
|
||||||
|
|
||||||
|
### Built-in Plugins
|
||||||
|
|
||||||
|
| Plugin | Location | Features |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| Timer | `internal/plugins/timer/` | Simplest complete example, registers one tool + interrupt feedback |
|
||||||
|
| CLI | `internal/plugins/cli/` | Unix socket listener + synchronous request-response |
|
||||||
|
| WebUI | `internal/plugins/webui/` | HTTP service + dependency injection |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Want to understand the project goals? See [OVERVIEW.md](OVERVIEW.md).*
|
||||||
|
*Want to understand the architecture? See [ARCHITECTURE.md](ARCHITECTURE.md).*
|
||||||
|
*SDK repository and development tools? See [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk).*
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
[English](../en/ADAPTER.md) | **中文**
|
||||||
|
|
||||||
# Lua Adapter — LLM 源适配指南
|
# Lua Adapter — LLM 源适配指南
|
||||||
|
|
||||||
每个 LLM API 源对应一个 Lua 脚本,负责请求转换(Go 统一格式 → API 格式)和响应转换(API 格式 → Go 统一格式)。
|
每个 LLM API 源对应一个 Lua 脚本,负责请求转换(Go 统一格式 → API 格式)和响应转换(API 格式 → Go 统一格式)。
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
[English](../en/ARCHITECTURE.md) | **中文**
|
||||||
|
|
||||||
# HomeAgent 架构
|
# HomeAgent 架构
|
||||||
|
|
||||||
内核零 IO,一切外界交互来自插件。
|
内核零 IO,一切外界交互来自插件。
|
||||||
@ -342,3 +344,4 @@ internal/
|
|||||||
├── supervisor/ — 守护进程管理
|
├── supervisor/ — 守护进程管理
|
||||||
├── snapshot/ — 快照
|
├── snapshot/ — 快照
|
||||||
└── tokenizer/ — 中文分词 (jieba 包装)
|
└── tokenizer/ — 中文分词 (jieba 包装)
|
||||||
|
```
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
[English](../en/OVERVIEW.md) | **中文**
|
||||||
|
|
||||||
# HomeAgent — 项目概览
|
# HomeAgent — 项目概览
|
||||||
|
|
||||||
## 这是什么
|
## 这是什么
|
||||||
@ -69,4 +71,4 @@ HomeAgent 是一个持续运行的个人智能 Agent 框架。
|
|||||||
|
|
||||||
- 内置插件:webui / cli / timer / cmd / mcp / agentcli / healthcheck / pluginmgr / openclaw / files
|
- 内置插件:webui / cli / timer / cmd / mcp / agentcli / healthcheck / pluginmgr / openclaw / files
|
||||||
- 外部插件示例([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库 `example/`,含 Go 和 Lua 两种类型):qq / files / web / memo / bili / editdoc / a2a / ocr / sanitizer / luaplugintest / testlua
|
- 外部插件示例([homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库 `example/`,含 Go 和 Lua 两种类型):qq / files / web / memo / bili / editdoc / a2a / ocr / sanitizer / luaplugintest / testlua
|
||||||
- 打包分发:`.hmap` 插件包格式,通过 WebUI 安装
|
- 打包分发:`.hmap` 插件包格式,通过 WebUI 安装
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
[English](../en/PLUGIN_DEV.md) | **中文**
|
||||||
|
|
||||||
# HomeAgent 插件开发指南
|
# HomeAgent 插件开发指南
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
@ -473,4 +475,4 @@ import (
|
|||||||
|
|
||||||
*了解项目整体目标?查看 [OVERVIEW.md](OVERVIEW.md)。*
|
*了解项目整体目标?查看 [OVERVIEW.md](OVERVIEW.md)。*
|
||||||
*了解技术架构?查看 [ARCHITECTURE.md](ARCHITECTURE.md)。*
|
*了解技术架构?查看 [ARCHITECTURE.md](ARCHITECTURE.md)。*
|
||||||
*SDK 仓库与开发工具?查看 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk)。*
|
*SDK 仓库与开发工具?查看 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk)。*
|
||||||
200
迭代文档.md
200
迭代文档.md
@ -1,200 +0,0 @@
|
|||||||
# 迭代文档
|
|
||||||
|
|
||||||
## 插件错误分析与修改建议(2026-07-11)
|
|
||||||
|
|
||||||
基于本机 homeagent 实例 24h systemd 日志分析。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 1. LLM Provider Fallback 链
|
|
||||||
|
|
||||||
#### 现象
|
|
||||||
DeepSeek 网络不稳定时,依次尝试 8 个 provider,全部失败耗时 ~10s:
|
|
||||||
```
|
|
||||||
provider "lua_deepseek" failed: TLS handshake timeout
|
|
||||||
provider "lua_anthropic" failed: 401 Invalid bearer token
|
|
||||||
provider "lua_gemini" failed: 404
|
|
||||||
provider "lua_github" failed: 401 Bad credentials
|
|
||||||
provider "lua_groq" failed: 401 Invalid API Key
|
|
||||||
provider "lua_mistral" failed: TLS handshake timeout / x509 cert mismatch
|
|
||||||
provider "lua_ollama" failed: 404 model 'llama3' not found
|
|
||||||
provider "lua_openai" failed: TLS handshake timeout
|
|
||||||
→ all 8 providers failed
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 根因定位
|
|
||||||
- fallback 逻辑在 `internal/agent/core/agent.go:671-698`:`OrderedProviders()` 遍历所有已注册 provider
|
|
||||||
- provider 来自 `cfg.LLM.Sources`(`cmd/homed/main.go:260-273`),**不是硬编码的循环**,是按配置依次尝试
|
|
||||||
- **但是**默认配置 `config/config.go:32-41` 的 `DefaultConfig()` 硬编码了 8 个 source。本机 `/etc/homeagent/config.yaml` 不存在,所以 fallback 到默认配置的 8 个 sources
|
|
||||||
- 本机启动日志确认:`sources=8 adapters=8`
|
|
||||||
- 认证错误(401/403/404)的 provider 每次 fallback 都重新尝试,没有缓存/跳过机制
|
|
||||||
|
|
||||||
#### 修改建议
|
|
||||||
|
|
||||||
**homeagent(高优先级)**
|
|
||||||
- `internal/agent/core/agent.go` fallback 循环:
|
|
||||||
- 对返回 401/403 的 provider 标记为 `auth_failed`,本轮不再重试
|
|
||||||
- 增加断路器:连续 N 次失败的 provider 进入 cooldown(如 5 分钟不选)
|
|
||||||
- `internal/agent/api/provider.go` `ProviderManager`:
|
|
||||||
- 增加 `MarkUnavailable(name string)` / `IsAvailable(name string)` 方法
|
|
||||||
- `config/config.go` `DefaultConfig()`:
|
|
||||||
- 默认只保留 primary source(deepseek),其余 7 个不作为默认配置
|
|
||||||
- 用户如果需要 fallback 应自行配置
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. QQ 插件 — `parse get_file ... bad response`
|
|
||||||
|
|
||||||
#### 现象
|
|
||||||
日志中出现频率最高,占全部 plugin 错误的 ~90%。NapCat 收到图片/文件事件后,homeagent 尝试下载但解析失败。
|
|
||||||
|
|
||||||
#### 根因定位
|
|
||||||
- 运行中的 QQ 插件是 `/home/newqqagent/plugins/qq/plugin.so`(13MB 编译产物)
|
|
||||||
- 源码 `.tmp-plugins/qq/plugin.go`(1051 行)与运行版本不一致(运行版本 line 1356 有此错误)
|
|
||||||
- 错误信息仅包含文件名,不包含 NapCat 原始响应,无法定位是 NapCat 返回了非 JSON 还是 URL 无效
|
|
||||||
|
|
||||||
#### 修改建议
|
|
||||||
|
|
||||||
**QQ 插件(高优先级)**
|
|
||||||
- `handleGetGroupFiles` download 分支(当前 line 892-933):
|
|
||||||
- JSON 解析失败时,将 NapCat 原始 body 截断后一并返回
|
|
||||||
- 对 NapCat 返回的错误状态码(如 `retcode != 0`)明确提示
|
|
||||||
- 例:`{"error": "parse response failed", "raw": "<body_preview>", "file_id": "xxx"}`
|
|
||||||
|
|
||||||
**homeagent SDK(中优先级)**
|
|
||||||
- `internal/sdk/plugin.go` 增加 `LogError(plugin, action, err, context)` 方法
|
|
||||||
- 统一插件错误日志格式:`[plugin] <name> <action>: <err> | <context>`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. QQ 插件 — `qq_web_fetch` 内容不完整
|
|
||||||
|
|
||||||
#### 现象
|
|
||||||
工具仅返回页面 `<title>` 内容(`标题: xxx`),部分站点:
|
|
||||||
- `502 Bad Gateway` — 目标站不可用
|
|
||||||
- `百度安全验证` — 触发反爬
|
|
||||||
- `www.zhihu.com` — 只有域名无内容
|
|
||||||
|
|
||||||
#### 根因
|
|
||||||
运行中的 QQ plugin.so 包含 `qq_web_fetch` 工具(源码未在仓库中),实现简陋,只提取 title 标签。
|
|
||||||
|
|
||||||
#### 修改建议
|
|
||||||
- 升级为完整正文提取(readability 算法)
|
|
||||||
- 增加 User-Agent 轮换
|
|
||||||
- 对非 200 状态码返回明确错误
|
|
||||||
- 考虑将 web_fetch 功能移至独立的 `web` 插件(已定义 manifest 但未编译 plugin.so)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. A2A 插件 — discover 超时
|
|
||||||
|
|
||||||
#### 现象
|
|
||||||
```
|
|
||||||
a2a_a2a_discover → http://192.168.1.100:8080/a2a/agent-card
|
|
||||||
→ context deadline exceeded
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 根因
|
|
||||||
- 目标地址 `192.168.1.100:8080` 不可达
|
|
||||||
- 错误信息只有"连接Agent失败",无超时/拒绝等分类
|
|
||||||
|
|
||||||
#### 修改建议
|
|
||||||
- A2A 插件的 discover 错误应区分:超时、连接拒绝、DNS 解析失败
|
|
||||||
- Agent Card URL 应从配置读取,而非硬编码
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. AgentCLI — SSH 终端超时
|
|
||||||
|
|
||||||
#### 现象
|
|
||||||
```
|
|
||||||
terminal_create: ssh ... timeout=15s
|
|
||||||
terminal_read: 终端 term_2 不存在或已关闭
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 根因
|
|
||||||
SSH 命令 timeout 仅 15s,连接建立后很快超时被清理。
|
|
||||||
|
|
||||||
#### 修改建议
|
|
||||||
- `terminal_create` 根据命令类型给出默认超时建议(SSH 建议 60-300s)
|
|
||||||
- 终端关闭时主动发送中断通知,而非让 agent 下次 read 才知道
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. LLM 思维泄漏 — 工具调用残留混入 QQ 消息内容
|
|
||||||
|
|
||||||
#### 现象
|
|
||||||
LLM 通过 `qq_send_private_msg` 发送的消息中,包含了原始工具调用描述。用户在 QQ 上收到类似:
|
|
||||||
```
|
|
||||||
好嘞老大,我找找日志给你发过来!😊
|
|
||||||
|
|
||||||
<tool_call>
|
|
||||||
cmd_run find / -name "*.log" 2>/dev/null | head -20
|
|
||||||
</tool_call>
|
|
||||||
```
|
|
||||||
|
|
||||||
LLM 在同一轮生成中同时输出文本(`content`)和工具调用(`tool_calls`),但文本内容里也描述/复现了它准备调用的工具。
|
|
||||||
|
|
||||||
#### 根因
|
|
||||||
- LLM API 返回格式:`{content: "文本...", tool_calls: [...]}` — content 和 tool_calls 是并列关系
|
|
||||||
- 部分 LLM(尤其是 thinking/reasoning 模型)会在 content 中输出推理过程,包括"我需要调 xxx 工具"的描述
|
|
||||||
- `qq_send_private_msg` 的 `message` 参数直接取 LLM 的 `content` 原文,未做清洗
|
|
||||||
- 工具调用残留不是框架 bug,是 LLM 输出内容的"思维泄漏"
|
|
||||||
|
|
||||||
#### 修改建议
|
|
||||||
|
|
||||||
**QQ 插件 `beforeOwnToolcall`(高优先级)**
|
|
||||||
- 对 `qq_send_private_msg` / `qq_send_group_msg` 的 `message` 参数做正则清洗:
|
|
||||||
- 过滤 `<tool_call>...</tool_call>`、`<invoke>...</invoke>` 等已知的 tool call 标记
|
|
||||||
- 过滤 `cmd_run`、`terminal_create` 等工具名后跟命令的文本模式
|
|
||||||
- 过滤 markdown 代码块中疑似 shell 命令的内容
|
|
||||||
- 清洗规则应可配置/可扩展
|
|
||||||
|
|
||||||
**homeagent 核心层 `post_action` 阶段(中优先级)**
|
|
||||||
- `internal/agent/core/agent.go` 中 `post_action` stage 后,对 LLM 返回的 `content` 增加通用清洗管道:
|
|
||||||
- 移除 ````xml <tool_call>...` 等已知模式
|
|
||||||
- 移除 `【tool_call】...` 等自定义标记
|
|
||||||
- 清洗后的 content 写入上下文 + 输出通道,原始 content 保留供调试
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. Files 插件 — `not a directory` 提示不友好
|
|
||||||
|
|
||||||
#### 现象
|
|
||||||
```
|
|
||||||
files_ls /tmp/hello_world.pptx → "not a directory: /tmp/hello_world.pptx"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 修改建议
|
|
||||||
- `handleLs` 遇到文件路径时,返回"这是一个文件,不是目录。如需读取文件内容,请使用 files_read 工具"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. 未编译插件
|
|
||||||
|
|
||||||
`/home/newqqagent/plugins/` 中以下插件有 `plugin.json` 但无 `plugin.so`:
|
|
||||||
- `web` — 声明提供 `web_search` / `web_fetch`
|
|
||||||
- `browser` — 声明提供 headless browser
|
|
||||||
- `bili` — B 站相关
|
|
||||||
- `memo` — 备忘录
|
|
||||||
- `editdoc` — 文档编辑
|
|
||||||
|
|
||||||
需要找到对应 Go 源码并编译部署。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 附录:相关源码位置
|
|
||||||
|
|
||||||
| 组件 | 路径 | 关键行 |
|
|
||||||
|------|------|--------|
|
|
||||||
| Provider 注册 | `cmd/homed/main.go` | 259-276 |
|
|
||||||
| ProviderManager | `internal/agent/api/provider.go` | 581-676 |
|
|
||||||
| Fallback 循环 | `internal/agent/core/agent.go` | 661-711 |
|
|
||||||
| 默认配置 | `config/config.go` | 15-67 |
|
|
||||||
| QQ 插件源码 | `.tmp-plugins/qq/plugin.go` | 全部(1051行) |
|
|
||||||
| QQ 插件运行版 | `/home/newqqagent/plugins/qq/plugin.so` | — |
|
|
||||||
| 插件注册 | `internal/plugin/registry.go` | 全部 |
|
|
||||||
| PluginSDK | `internal/sdk/plugin.go` | 全部 |
|
|
||||||
| AgentCLI 终端 | `internal/plugins/agentcli/plugin.go` | 全部 |
|
|
||||||
| Files 插件 | `internal/plugins/files/plugin.go` | 全部 |
|
|
||||||
| Cmd 插件 | `internal/plugins/cmd/plugin.go` | 全部 |
|
|
||||||
Reference in New Issue
Block a user