mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-05 00:45:29 +08:00
91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
package promptcompat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"ds2api/internal/toolcall"
|
|
)
|
|
|
|
func injectToolPrompt(messages []map[string]any, tools []any, policy ToolChoicePolicy) ([]map[string]any, []string) {
|
|
if policy.IsNone() {
|
|
return messages, nil
|
|
}
|
|
toolSchemas := make([]string, 0, len(tools))
|
|
names := make([]string, 0, len(tools))
|
|
isAllowed := func(name string) bool {
|
|
if strings.TrimSpace(name) == "" {
|
|
return false
|
|
}
|
|
if len(policy.Allowed) == 0 {
|
|
return true
|
|
}
|
|
_, ok := policy.Allowed[name]
|
|
return ok
|
|
}
|
|
|
|
for _, t := range tools {
|
|
tool, ok := t.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
name, desc, schema := toolcall.ExtractToolMeta(tool)
|
|
name = strings.TrimSpace(name)
|
|
if !isAllowed(name) {
|
|
continue
|
|
}
|
|
names = append(names, name)
|
|
if desc == "" {
|
|
desc = "No description available"
|
|
}
|
|
b, _ := json.Marshal(schema)
|
|
toolSchemas = append(toolSchemas, fmt.Sprintf("Tool: %s\nDescription: %s\nParameters: %s", name, desc, string(b)))
|
|
}
|
|
if len(toolSchemas) == 0 {
|
|
return messages, names
|
|
}
|
|
toolPrompt := "You have access to these tools:\n\n" + strings.Join(toolSchemas, "\n\n") + "\n\n" + toolcall.BuildToolCallInstructions(names)
|
|
if hasReadLikeTool(names) {
|
|
toolPrompt += "\n\nRead-tool cache guard: If a Read/read_file-style tool result says the file is unchanged, already available in history, should be referenced from previous context, or otherwise provides no file body, treat that result as missing content. Do not repeatedly call the same read request for that missing body. Request a full-content read if the tool supports it, or tell the user that the file contents need to be provided again."
|
|
}
|
|
if policy.Mode == ToolChoiceRequired {
|
|
toolPrompt += "\n7) For this response, you MUST call at least one tool from the allowed list."
|
|
}
|
|
if policy.Mode == ToolChoiceForced && strings.TrimSpace(policy.ForcedName) != "" {
|
|
toolPrompt += "\n7) For this response, you MUST call exactly this tool name: " + strings.TrimSpace(policy.ForcedName)
|
|
toolPrompt += "\n8) Do not call any other tool."
|
|
}
|
|
|
|
for i := range messages {
|
|
if messages[i]["role"] == "system" {
|
|
old, _ := messages[i]["content"].(string)
|
|
messages[i]["content"] = strings.TrimSpace(old + "\n\n" + toolPrompt)
|
|
return messages, names
|
|
}
|
|
}
|
|
messages = append([]map[string]any{{"role": "system", "content": toolPrompt}}, messages...)
|
|
return messages, names
|
|
}
|
|
|
|
func hasReadLikeTool(names []string) bool {
|
|
for _, name := range names {
|
|
switch normalizeToolNameForGuard(name) {
|
|
case "read", "readfile":
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func normalizeToolNameForGuard(name string) string {
|
|
var b strings.Builder
|
|
for _, r := range strings.ToLower(strings.TrimSpace(name)) {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|