feat: implement streaming chat, skill routing, and SAFe PI planning tools
- Add /api/chat/stream endpoint with Server-Sent Events (SSE) for real-time message streaming * Implement StreamEvent types (thought, tool_call, tool_result, final, error) * Add StreamEventCallback mechanism for event propagation * Create StreamChatHandler in webui/bot with proper HTTP headers and flushing - Implement LLM-based skill router for intelligent capability selection * Add optional routerLLM client for semantic routing * Implement routeSkillsWithLLM() to match user intent to available skills * Add matchSkillsByName() for fuzzy skill matching * Update buildUnifiedSystemPrompt() to use routed skills - Add streaming support to ReAct pipeline * Implement runUnifiedReActStream() for streaming thought/action/observation * Emit StreamEvent at each ReAct step * Support callback error handling in streaming mode - Integrate three new DevOps tools * tools/filedoc: Extract document content from file_id via OpenAI * tools/giteaticket: Create Gitea issues from PI plan items with SAFe metadata * tools/piplan: Publish PI planning blueprints with dependency tracking - Add SAFe PI Planning skill * Implement PM/SA/RTE (iron triangle) workflow * Support for Feature, Enabler, and Dependency definition * Automatic task decomposition and Gitea integration - Create frontend integration documentation * Complete SSE protocol specification * TypeScript fetch + ReadableStream example * LLM-ready refactoring template for other projects - Simplify file handling * Remove legacy file context structures and dual-mode processing * Consolidate file operations into UploadAndCacheFiles() * Remove FilePromptMode configuration and related complexity - Update configuration * Add Router model support (LLM_ROUTER_MODEL) * Add Gitea configuration (BaseURL, Token, Owner, Repo) * WebSearch and additional tool infrastructure Tests: All 22 test packages passing, 8/8 webui tests including 3 new stream tests
This commit is contained in:
@@ -46,3 +46,50 @@ func TestFormatRuntimeContextForPromptIncludesGOOS(t *testing.T) {
|
||||
t.Fatalf("expected runtime context contains GOOS=%s, got: %s", runtime.GOOS, doc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchSkillsByNameExact(t *testing.T) {
|
||||
all := []knowledge.Skill{
|
||||
{Name: "SAFe PI Planning", Content: "PI规划技能"},
|
||||
{Name: "文件系统查询专家", Content: "文件查询"},
|
||||
{Name: "代码生成", Content: "代码生成技能"},
|
||||
}
|
||||
matched := matchSkillsByName(all, []string{"SAFe PI Planning"})
|
||||
if len(matched) != 1 {
|
||||
t.Fatalf("expected 1 match, got %d", len(matched))
|
||||
}
|
||||
if matched[0].Name != "SAFe PI Planning" {
|
||||
t.Fatalf("expected SAFe PI Planning, got %s", matched[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchSkillsByNameFuzzy(t *testing.T) {
|
||||
all := []knowledge.Skill{
|
||||
{Name: "SAFe PI Planning", Content: "PI规划技能"},
|
||||
{Name: "文件系统查询专家", Content: "文件查询"},
|
||||
}
|
||||
matched := matchSkillsByName(all, []string{"pi planning", "文件"})
|
||||
if len(matched) != 2 {
|
||||
t.Fatalf("expected 2 matches, got %d", len(matched))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchSkillsByNameNoMatch(t *testing.T) {
|
||||
all := []knowledge.Skill{
|
||||
{Name: "文件系统查询专家", Content: "文件查询"},
|
||||
}
|
||||
matched := matchSkillsByName(all, []string{"不存在的技能"})
|
||||
if len(matched) != 0 {
|
||||
t.Fatalf("expected 0 matches, got %d", len(matched))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchSkillsByNameEmpty(t *testing.T) {
|
||||
matched := matchSkillsByName(nil, []string{"any"})
|
||||
if len(matched) != 0 {
|
||||
t.Fatalf("expected 0 matches, got %d", len(matched))
|
||||
}
|
||||
matched = matchSkillsByName([]knowledge.Skill{{Name: "test"}}, nil)
|
||||
if len(matched) != 0 {
|
||||
t.Fatalf("expected 0 matches, got %d", len(matched))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user