mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 18:15:34 +00:00
fix(providers): correct Anthropic prompt caching + add datetime tool
- Move cache_control from request root (ignored by API) to per-block placement on last system block and last tool definition - Change system prompt time format to date-only for better cache stability - Add builtin datetime tool for precise timestamps (cron, memory, etc.) - Add atMs past-time validation in cron handleUpdate (was only in handleAdd) - Update cron description to guide model to use datetime tool first
This commit is contained in:
1 parent
9468aae422
commit
0857321a6b
6 files changed
+81
-5
No files matched your search
@@ -165,6 +165,9 @@ func runGateway() {
|
||||
skillsLoader, skillSearchTool, globalSkillsDir := setupSkillsSystem(cfg, workspace, dataDir, pgStores, toolsReg, providerRegistry, msgBus)
|
||||
_ = skillSearchTool // used via wireExtras → skillsLoader; kept for type clarity
|
||||
|
||||
// DateTime tool (precise time for cron scheduling, memory timestamps, etc.)
|
||||
toolsReg.Register(tools.NewDateTimeTool())
|
||||
|
||||
// Cron tool (agent-facing, matching TS cron-tool.ts)
|
||||
toolsReg.Register(tools.NewCronTool(pgStores.Cron))
|
||||
slog.Info("cron tool registered")
|
||||
|
||||
@@ -67,6 +67,7 @@ var coreToolSummaries = map[string]string{
|
||||
"spawn": "Spawn a self-clone subagent to handle a task in the background",
|
||||
"web_search": "Search the web",
|
||||
"web_fetch": "Fetch and extract content from a URL",
|
||||
"datetime": "Get current date/time with timezone support — use before creating cron jobs or time-sensitive operations",
|
||||
"cron": "Manage scheduled jobs and reminders",
|
||||
"skill_search": "Search available skills by keyword (weather, translate, github, etc.)",
|
||||
"use_skill": "Invoke a skill by name and follow its instructions",
|
||||
|
||||
@@ -86,7 +86,7 @@ func buildUserIdentitySection(ownerIDs []string) []string {
|
||||
func buildTimeSection() []string {
|
||||
now := time.Now()
|
||||
return []string{
|
||||
fmt.Sprintf("Current time: %s (UTC)", now.UTC().Format("2006-01-02 15:04 Monday")),
|
||||
fmt.Sprintf("Current date: %s (UTC)", now.UTC().Format("2006-01-02 Monday")),
|
||||
"",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,11 +145,15 @@ func (p *AnthropicProvider) buildRequestBody(model string, req ChatRequest, stre
|
||||
}
|
||||
}
|
||||
|
||||
// Add cache_control breakpoint to the last system block (caches system prompt prefix).
|
||||
if len(systemBlocks) > 0 {
|
||||
systemBlocks[len(systemBlocks)-1]["cache_control"] = map[string]any{"type": "ephemeral"}
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"model": model,
|
||||
"max_tokens": 4096,
|
||||
"messages": messages,
|
||||
"cache_control": map[string]any{"type": "ephemeral"},
|
||||
"model": model,
|
||||
"max_tokens": 4096,
|
||||
"messages": messages,
|
||||
}
|
||||
|
||||
if stream {
|
||||
@@ -172,6 +176,10 @@ func (p *AnthropicProvider) buildRequestBody(model string, req ChatRequest, stre
|
||||
}
|
||||
tools = append(tools, tool)
|
||||
}
|
||||
// Add cache_control breakpoint to the last tool (caches tool definitions prefix).
|
||||
if len(tools) > 0 {
|
||||
tools[len(tools)-1]["cache_control"] = map[string]any{"type": "ephemeral"}
|
||||
}
|
||||
body["tools"] = tools
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ CRITICAL CONSTRAINTS:
|
||||
- message is required for add action
|
||||
- schedule is required for add action
|
||||
- Default: jobs run as isolated agent turns with the specified message
|
||||
- Before creating or updating a scheduled job, call the datetime tool first to get the precise current time and unix_ms timestamp. Do NOT guess or estimate timestamps.
|
||||
|
||||
Use jobId as the canonical identifier; id is accepted for compatibility.`
|
||||
}
|
||||
@@ -309,6 +310,13 @@ func (t *CronTool) handleUpdate(args map[string]any, agentID, userID string) *Re
|
||||
patchJSON, _ := json.Marshal(patchObj)
|
||||
json.Unmarshal(patchJSON, &patch)
|
||||
|
||||
// Validate atMs not in the past when updating schedule
|
||||
if patch.Schedule != nil && patch.Schedule.Kind == "at" && patch.Schedule.AtMS != nil {
|
||||
if *patch.Schedule.AtMS <= time.Now().UnixMilli() {
|
||||
return ErrorResult(fmt.Sprintf("schedule.atMs is in the past (%d). Use the datetime tool to get current time, then set a future timestamp. Current time is %d ms", *patch.Schedule.AtMS, time.Now().UnixMilli()))
|
||||
}
|
||||
}
|
||||
|
||||
job, err := t.cronStore.UpdateJob(jobID, patch)
|
||||
if err != nil {
|
||||
return ErrorResult(fmt.Sprintf("failed to update cron job: %v", err))
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DateTimeTool provides precise current date/time with timezone support.
|
||||
// Models use this before creating cron jobs or any time-sensitive operation
|
||||
// instead of guessing timestamps from the system prompt's date-only field.
|
||||
type DateTimeTool struct{}
|
||||
|
||||
func NewDateTimeTool() *DateTimeTool { return &DateTimeTool{} }
|
||||
|
||||
func (t *DateTimeTool) Name() string { return "datetime" }
|
||||
|
||||
func (t *DateTimeTool) Description() string {
|
||||
return `Get the current date and time. Use this when you need precise timestamps for scheduling (cron jobs), logging, or any time-sensitive operation.
|
||||
|
||||
Returns current time in both UTC and the requested timezone.
|
||||
If no timezone is provided, returns UTC only.`
|
||||
}
|
||||
|
||||
func (t *DateTimeTool) Parameters() map[string]any {
|
||||
return map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"timezone": map[string]any{
|
||||
"type": "string",
|
||||
"description": "IANA timezone name (e.g. 'Asia/Ho_Chi_Minh', 'America/New_York'). If omitted, returns UTC only.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DateTimeTool) Execute(_ context.Context, args map[string]any) *Result {
|
||||
now := time.Now()
|
||||
result := map[string]any{
|
||||
"utc": now.UTC().Format(time.RFC3339),
|
||||
"unix_ms": now.UnixMilli(),
|
||||
}
|
||||
|
||||
if tz, ok := args["timezone"].(string); ok && tz != "" {
|
||||
loc, err := time.LoadLocation(tz)
|
||||
if err != nil {
|
||||
return ErrorResult(fmt.Sprintf("invalid timezone '%s': use IANA names like 'Asia/Ho_Chi_Minh', 'America/New_York'", tz))
|
||||
}
|
||||
result["local"] = now.In(loc).Format(time.RFC3339)
|
||||
result["timezone"] = tz
|
||||
}
|
||||
|
||||
data, _ := json.MarshalIndent(result, "", " ")
|
||||
return NewResult(string(data))
|
||||
}
|
||||
Reference in new issue
Block a user