Skip to content

Commit ab97ad7

Browse files
authored
Merge pull request #1433 from trheyi/main
Enhance Agent Documentation and Add Extract Command Functionality
2 parents f108be7 + 235084d commit ab97ad7

File tree

18 files changed

+1252
-58
lines changed

18 files changed

+1252
-58
lines changed

agent/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,14 @@ function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
6969
### 3. Test (Optional)
7070

7171
```bash
72+
# Run tests
7273
yao agent test -i "Hello, how are you?"
74+
75+
# Run tests from JSONL file
76+
yao agent test -i tests/inputs.jsonl -v
77+
78+
# Extract results for review
79+
yao agent extract output-*.jsonl
7380
```
7481

7582
### 4. Run

agent/robot/DESIGN.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,9 +431,16 @@ For each task:
431431
| Type | ExecutorID Format | Example |
432432
|------|-------------------|---------|
433433
| `assistant` | Agent ID | `experts.text-writer` |
434-
| `mcp` | `clientID.toolName` | `filesystem.read_file` |
434+
| `mcp` | `mcp_server.mcp_tool` | `ark.image.text2img.generate` |
435435
| `process` | Process name | `models.user.Find` |
436436

437+
**MCP Task Fields:**
438+
439+
For MCP tasks, three fields are required:
440+
- `executor_id`: Combined format `mcp_server.mcp_tool`
441+
- `mcp_server`: MCP server/client ID (e.g., `ark.image.text2img`)
442+
- `mcp_tool`: Tool name within the server (e.g., `generate`)
443+
437444
**Multi-Turn Conversation Flow:**
438445

439446
For assistant tasks, P3 uses a multi-turn conversation approach:

agent/robot/TECHNICAL.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1322,9 +1322,13 @@ type Task struct {
13221322
13231323
// Executor
13241324
ExecutorType ExecutorType `json:"executor_type"`
1325-
ExecutorID string `json:"executor_id"`
1325+
ExecutorID string `json:"executor_id"` // unified ID: agent/assistant/process ID, or "mcp_server.mcp_tool" for MCP
13261326
Args []any `json:"args,omitempty"`
13271327
1328+
// MCP-specific fields (required when executor_type is "mcp")
1329+
MCPServer string `json:"mcp_server,omitempty"` // MCP server/client ID (e.g., "ark.image.text2img")
1330+
MCPTool string `json:"mcp_tool,omitempty"` // MCP tool name (e.g., "generate")
1331+
13281332
// Validation (defined in P2, used in P3)
13291333
ExpectedOutput string `json:"expected_output,omitempty"` // what the task should produce
13301334
// ValidationRules supports two formats:

agent/robot/api/robot.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func loadRobotFromDB(memberID string) (*types.Robot, error) {
166166
Select: []interface{}{
167167
"id", "member_id", "team_id", "display_name", "bio",
168168
"system_prompt", "robot_status", "autonomous_mode",
169-
"robot_config", "robot_email",
169+
"robot_config", "robot_email", "agents", "mcp_servers",
170170
},
171171
Wheres: []model.QueryWhere{
172172
{Column: "member_id", Value: memberID},
@@ -228,7 +228,7 @@ func listRobotsFromDB(query *ListQuery) (*ListResult, error) {
228228
Select: []interface{}{
229229
"id", "member_id", "team_id", "display_name", "bio",
230230
"system_prompt", "robot_status", "autonomous_mode",
231-
"robot_config", "robot_email",
231+
"robot_config", "robot_email", "agents", "mcp_servers",
232232
},
233233
Wheres: wheres,
234234
Orders: orders,

agent/robot/cache/load.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ var memberFields = []interface{}{
2424
"autonomous_mode",
2525
"robot_config",
2626
"robot_email",
27+
"agents",
28+
"mcp_servers",
2729
}
2830

2931
// SetMemberModel sets the member model name

agent/robot/executor/standard/input.go

Lines changed: 185 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package standard
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"strings"
78

9+
"github.com/yaoapp/gou/mcp"
10+
"github.com/yaoapp/yao/agent/assistant"
811
agentcontext "github.com/yaoapp/yao/agent/context"
12+
"github.com/yaoapp/yao/agent/i18n"
913
robottypes "github.com/yaoapp/yao/agent/robot/types"
1014
)
1115

@@ -39,47 +43,62 @@ func (f *InputFormatter) FormatClockContext(clock *robottypes.ClockContext, robo
3943
sb.WriteString(fmt.Sprintf("- **Day**: %s\n", clock.DayOfWeek))
4044
sb.WriteString(fmt.Sprintf("- **Date**: %d/%d/%d\n", clock.Year, clock.Month, clock.DayOfMonth))
4145
sb.WriteString(fmt.Sprintf("- **Week**: %d of year\n", clock.WeekOfYear))
46+
sb.WriteString(fmt.Sprintf("- **Hour**: %d\n", clock.Hour))
4247
sb.WriteString(fmt.Sprintf("- **Timezone**: %s\n", clock.TZ))
4348

44-
// Time markers
49+
// Time markers - show all markers with check/cross for context awareness
4550
sb.WriteString("\n### Time Markers\n")
46-
if clock.IsWeekend {
47-
sb.WriteString("- ✓ Weekend\n")
48-
}
49-
if clock.IsMonthStart {
50-
sb.WriteString("- ✓ Month Start (1st-3rd)\n")
51-
}
52-
if clock.IsMonthEnd {
53-
sb.WriteString("- ✓ Month End (last 3 days)\n")
54-
}
55-
if clock.IsQuarterEnd {
56-
sb.WriteString("- ✓ Quarter End\n")
57-
}
58-
if clock.IsYearEnd {
59-
sb.WriteString("- ✓ Year End\n")
60-
}
61-
62-
// Robot identity section (if available)
63-
if robot != nil && robot.Config != nil && robot.Config.Identity != nil {
64-
sb.WriteString("\n## Robot Identity\n\n")
65-
sb.WriteString(fmt.Sprintf("- **Role**: %s\n", robot.Config.Identity.Role))
66-
if len(robot.Config.Identity.Duties) > 0 {
67-
sb.WriteString("- **Duties**:\n")
68-
for _, duty := range robot.Config.Identity.Duties {
69-
sb.WriteString(fmt.Sprintf(" - %s\n", duty))
51+
sb.WriteString(fmt.Sprintf("- %s Weekend\n", boolMark(clock.IsWeekend)))
52+
sb.WriteString(fmt.Sprintf("- %s Month Start (1st-3rd)\n", boolMark(clock.IsMonthStart)))
53+
sb.WriteString(fmt.Sprintf("- %s Month End (last 3 days)\n", boolMark(clock.IsMonthEnd)))
54+
sb.WriteString(fmt.Sprintf("- %s Quarter End\n", boolMark(clock.IsQuarterEnd)))
55+
sb.WriteString(fmt.Sprintf("- %s Year End\n", boolMark(clock.IsYearEnd)))
56+
57+
// Robot identity section
58+
// Priority: Config.Identity > Robot fields (DisplayName, Bio, SystemPrompt)
59+
if robot != nil {
60+
if robot.Config != nil && robot.Config.Identity != nil {
61+
// Use structured identity from config
62+
sb.WriteString("\n## Robot Identity\n\n")
63+
sb.WriteString(fmt.Sprintf("- **Role**: %s\n", robot.Config.Identity.Role))
64+
if len(robot.Config.Identity.Duties) > 0 {
65+
sb.WriteString("- **Duties**:\n")
66+
for _, duty := range robot.Config.Identity.Duties {
67+
sb.WriteString(fmt.Sprintf(" - %s\n", duty))
68+
}
7069
}
71-
}
72-
if len(robot.Config.Identity.Rules) > 0 {
73-
sb.WriteString("- **Rules**:\n")
74-
for _, rule := range robot.Config.Identity.Rules {
75-
sb.WriteString(fmt.Sprintf(" - %s\n", rule))
70+
if len(robot.Config.Identity.Rules) > 0 {
71+
sb.WriteString("- **Rules**:\n")
72+
for _, rule := range robot.Config.Identity.Rules {
73+
sb.WriteString(fmt.Sprintf(" - %s\n", rule))
74+
}
75+
}
76+
} else if robot.DisplayName != "" || robot.Bio != "" || robot.SystemPrompt != "" {
77+
// Fallback: build identity from Robot fields (from __yao.member table)
78+
sb.WriteString("\n## Robot Identity\n\n")
79+
if robot.DisplayName != "" {
80+
sb.WriteString(fmt.Sprintf("- **Role**: %s\n", robot.DisplayName))
81+
}
82+
if robot.Bio != "" {
83+
sb.WriteString(fmt.Sprintf("- **Description**: %s\n", robot.Bio))
84+
}
85+
if robot.SystemPrompt != "" {
86+
sb.WriteString(fmt.Sprintf("- **Instructions**:\n%s\n", robot.SystemPrompt))
7687
}
7788
}
7889
}
7990

8091
return sb.String()
8192
}
8293

94+
// boolMark returns ✓ for true and ✗ for false
95+
func boolMark(v bool) string {
96+
if v {
97+
return "✓"
98+
}
99+
return "✗"
100+
}
101+
83102
// FormatRobotIdentity formats robot identity as user message content
84103
// Used to provide context about the robot's role and duties
85104
func (f *InputFormatter) FormatRobotIdentity(robot *robottypes.Robot) string {
@@ -115,43 +134,168 @@ func (f *InputFormatter) FormatRobotIdentity(robot *robottypes.Robot) string {
115134
// This is critical for generating achievable goals - without knowing available tools,
116135
// the agent might generate goals that cannot be accomplished
117136
func (f *InputFormatter) FormatAvailableResources(robot *robottypes.Robot) string {
137+
locale := "en" // default locale
138+
if robot != nil && robot.Config != nil {
139+
locale = robot.Config.GetDefaultLocale()
140+
}
141+
return f.FormatAvailableResourcesWithLocale(robot, locale)
142+
}
143+
144+
// FormatAvailableResourcesWithLocale formats available resources with specific locale for i18n support
145+
func (f *InputFormatter) FormatAvailableResourcesWithLocale(robot *robottypes.Robot, locale string) string {
118146
if robot == nil || robot.Config == nil {
119147
return ""
120148
}
121149

122150
var sb strings.Builder
123151
hasContent := false
124152

125-
// Available Agents
153+
// Available Agents - with detailed information (name, description)
126154
if robot.Config.Resources != nil && len(robot.Config.Resources.Agents) > 0 {
127155
if !hasContent {
128156
sb.WriteString("## Available Resources\n\n")
129157
hasContent = true
130158
}
131159
sb.WriteString("### Agents\n")
132-
sb.WriteString("These are the AI assistants you can delegate tasks to:\n")
133-
for _, agent := range robot.Config.Resources.Agents {
134-
sb.WriteString(fmt.Sprintf("- **%s**\n", agent))
160+
sb.WriteString("These are the AI assistants you can delegate tasks to:\n\n")
161+
for _, agentID := range robot.Config.Resources.Agents {
162+
// Try to get agent details
163+
ast, err := assistant.Get(agentID)
164+
if err != nil {
165+
// Fallback to just ID if agent not found
166+
sb.WriteString(fmt.Sprintf("- **%s**\n", agentID))
167+
continue
168+
}
169+
170+
// Get localized name and description
171+
name := i18n.Translate(agentID, locale, ast.Name).(string)
172+
description := ""
173+
if ast.Description != "" {
174+
description = i18n.Translate(agentID, locale, ast.Description).(string)
175+
}
176+
177+
// Format agent info
178+
sb.WriteString(fmt.Sprintf("- **%s** (`%s`)\n", name, agentID))
179+
if description != "" {
180+
sb.WriteString(fmt.Sprintf(" - %s\n", description))
181+
}
135182
}
136183
sb.WriteString("\n")
137184
}
138185

139-
// Available MCP Tools
186+
// Available MCP Tools - with detailed tool information
140187
if robot.Config.Resources != nil && len(robot.Config.Resources.MCP) > 0 {
141188
if !hasContent {
142189
sb.WriteString("## Available Resources\n\n")
143190
hasContent = true
144191
}
145192
sb.WriteString("### MCP Tools\n")
146-
sb.WriteString("These are the external tools and services you can use:\n")
147-
for _, mcp := range robot.Config.Resources.MCP {
148-
if len(mcp.Tools) > 0 {
149-
sb.WriteString(fmt.Sprintf("- **%s**: %s\n", mcp.ID, strings.Join(mcp.Tools, ", ")))
193+
sb.WriteString("These are the external tools and services you can use:\n\n")
194+
for _, mcpConfig := range robot.Config.Resources.MCP {
195+
// Try to get MCP client and list tools
196+
client, err := mcp.Select(mcpConfig.ID)
197+
if err != nil {
198+
// Fallback to basic info if client not found
199+
if len(mcpConfig.Tools) > 0 {
200+
sb.WriteString(fmt.Sprintf("- **%s**: %s\n", mcpConfig.ID, strings.Join(mcpConfig.Tools, ", ")))
201+
} else {
202+
sb.WriteString(fmt.Sprintf("- **%s**: all tools available\n", mcpConfig.ID))
203+
}
204+
continue
205+
}
206+
207+
// Get client info for name and description
208+
clientInfo := client.Info()
209+
clientName := mcpConfig.ID
210+
clientDesc := ""
211+
if clientInfo != nil {
212+
if clientInfo.Name != "" {
213+
clientName = clientInfo.Name
214+
}
215+
if clientInfo.Description != "" {
216+
clientDesc = clientInfo.Description
217+
}
218+
}
219+
220+
// Write MCP header
221+
if clientDesc != "" {
222+
sb.WriteString(fmt.Sprintf("#### %s (`%s`)\n", clientName, mcpConfig.ID))
223+
sb.WriteString(fmt.Sprintf("%s\n\n", clientDesc))
224+
} else {
225+
sb.WriteString(fmt.Sprintf("#### %s (`%s`)\n\n", clientName, mcpConfig.ID))
226+
}
227+
228+
// Try to list tools with context
229+
ctx := context.Background()
230+
toolsResp, err := client.ListTools(ctx, "")
231+
if err != nil || toolsResp == nil {
232+
// Fallback to configured tools
233+
if len(mcpConfig.Tools) > 0 {
234+
sb.WriteString("Available tools: ")
235+
sb.WriteString(strings.Join(mcpConfig.Tools, ", "))
236+
sb.WriteString("\n\n")
237+
} else {
238+
sb.WriteString("All tools available\n\n")
239+
}
240+
continue
241+
}
242+
243+
// Filter tools if specific tools are configured
244+
toolsToShow := toolsResp.Tools
245+
if len(mcpConfig.Tools) > 0 {
246+
// Create a map for quick lookup
247+
allowedTools := make(map[string]bool)
248+
for _, t := range mcpConfig.Tools {
249+
allowedTools[t] = true
250+
}
251+
// Filter tools
252+
var filteredTools []struct {
253+
Name string
254+
Description string
255+
}
256+
for _, tool := range toolsResp.Tools {
257+
if allowedTools[tool.Name] {
258+
filteredTools = append(filteredTools, struct {
259+
Name string
260+
Description string
261+
}{tool.Name, tool.Description})
262+
}
263+
}
264+
// Write filtered tools
265+
if len(filteredTools) > 0 {
266+
sb.WriteString("| Tool | Description |\n")
267+
sb.WriteString("|------|-------------|\n")
268+
for _, tool := range filteredTools {
269+
desc := tool.Description
270+
if len(desc) > 100 {
271+
desc = desc[:97] + "..."
272+
}
273+
// Escape pipe characters in description
274+
desc = strings.ReplaceAll(desc, "|", "\\|")
275+
sb.WriteString(fmt.Sprintf("| `%s` | %s |\n", tool.Name, desc))
276+
}
277+
} else {
278+
sb.WriteString("Configured tools: ")
279+
sb.WriteString(strings.Join(mcpConfig.Tools, ", "))
280+
}
281+
} else if len(toolsToShow) > 0 {
282+
// Show all available tools
283+
sb.WriteString("| Tool | Description |\n")
284+
sb.WriteString("|------|-------------|\n")
285+
for _, tool := range toolsToShow {
286+
desc := tool.Description
287+
if len(desc) > 100 {
288+
desc = desc[:97] + "..."
289+
}
290+
// Escape pipe characters in description
291+
desc = strings.ReplaceAll(desc, "|", "\\|")
292+
sb.WriteString(fmt.Sprintf("| `%s` | %s |\n", tool.Name, desc))
293+
}
150294
} else {
151-
sb.WriteString(fmt.Sprintf("- **%s**: all tools available\n", mcp.ID))
295+
sb.WriteString("No tools available\n")
152296
}
297+
sb.WriteString("\n")
153298
}
154-
sb.WriteString("\n")
155299
}
156300

157301
// Available Knowledge Base

0 commit comments

Comments
 (0)