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:
74
tools/filedoc/filedoc_test.go
Normal file
74
tools/filedoc/filedoc_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package filedoc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNameAndDescription(t *testing.T) {
|
||||
tool := New(Config{APIKey: "k", Model: "gpt-4o-mini"}, 5000, nil)
|
||||
if tool.Name() != "extract_file_document" {
|
||||
t.Fatalf("unexpected tool name: %s", tool.Name())
|
||||
}
|
||||
if tool.Description() == "" {
|
||||
t.Fatal("description should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInputPlainFileID(t *testing.T) {
|
||||
id, focus, err := parseInput("file_ec_452e96aad38940229058f193f5c5b9c6_12553222")
|
||||
if err != nil {
|
||||
t.Fatalf("parseInput returned error: %v", err)
|
||||
}
|
||||
if id != "file_ec_452e96aad38940229058f193f5c5b9c6_12553222" {
|
||||
t.Fatalf("unexpected id: %s", id)
|
||||
}
|
||||
if focus != "" {
|
||||
t.Fatalf("expected empty focus, got: %q", focus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInputFileIDSchemeWithFocus(t *testing.T) {
|
||||
input := "fileid://file_ec_12345\n重点关注风险与建议"
|
||||
id, focus, err := parseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("parseInput returned error: %v", err)
|
||||
}
|
||||
if id != "file_ec_12345" {
|
||||
t.Fatalf("unexpected id: %s", id)
|
||||
}
|
||||
if focus == "" {
|
||||
t.Fatal("expected non-empty focus")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInputJSON(t *testing.T) {
|
||||
input := `{"file_id":"file_ec_888", "focus":"提取关键结论"}`
|
||||
id, focus, err := parseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("parseInput returned error: %v", err)
|
||||
}
|
||||
if id != "file_ec_888" {
|
||||
t.Fatalf("unexpected id: %s", id)
|
||||
}
|
||||
if focus != "提取关键结论" {
|
||||
t.Fatalf("unexpected focus: %s", focus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInputInvalid(t *testing.T) {
|
||||
_, _, err := parseInput("hello world")
|
||||
if err == nil {
|
||||
t.Fatal("expected parse error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildExtractionPrompt(t *testing.T) {
|
||||
p := buildExtractionPrompt("file_ec_abc", "关注测试条目")
|
||||
if p == "" {
|
||||
t.Fatal("prompt should not be empty")
|
||||
}
|
||||
if p != "" && !(strings.Contains(p, "file_ec_abc") && strings.Contains(p, "关注测试条目")) {
|
||||
t.Fatalf("unexpected prompt content: %s", p)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user