Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion python/valuecell/core/coordinate/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class PlannerInput(BaseModel):
when determining what tasks should be executed.
"""

desired_agent_name: str = Field(
target_agent_name: str = Field(
..., description="The name of the agent the user wants to use for the task"
)
query: str = Field(
Expand Down
10 changes: 7 additions & 3 deletions python/valuecell/core/coordinate/planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
from agno.models.openrouter import OpenRouter

from valuecell.core.agent.connect import RemoteConnections
from valuecell.core.coordinate.planner_prompts import PLANNER_INSTRUCTIONS
from valuecell.core.coordinate.planner_prompts import (
PLANNER_EXPECTED_OUTPUT,
PLANNER_INSTRUCTION,
)
from valuecell.core.task import Task, TaskPattern, TaskStatus
from valuecell.core.types import UserInput
from valuecell.utils import generate_uuid
Expand Down Expand Up @@ -160,7 +163,8 @@ async def _analyze_input_and_create_tasks(
],
markdown=False,
debug_mode=agent_debug_mode_enabled(),
instructions=[PLANNER_INSTRUCTIONS],
instructions=[PLANNER_INSTRUCTION],
expected_output=PLANNER_EXPECTED_OUTPUT,
# context
db=InMemoryDb(),
add_datetime_to_context=True,
Expand All @@ -173,7 +177,7 @@ async def _analyze_input_and_create_tasks(
# Execute planning with the agent
run_response = agent.run(
PlannerInput(
desired_agent_name=user_input.desired_agent_name,
target_agent_name=user_input.target_agent_name,
query=user_input.query,
),
session_id=conversation_id,
Expand Down
243 changes: 214 additions & 29 deletions python/valuecell/core/coordinate/planner_prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,73 @@
"""

# noqa: E501
PLANNER_INSTRUCTIONS = """
PLANNER_INSTRUCTION = """
<purpose>
You are an AI Agent execution planner that analyzes user requests and creates executable task plans using available agents.
</purpose>

<core_process>
**If user specifies a target agent:**
1. **Verify agent exists**: Call `get_agent_card` to confirm the agent is available
2. **Check principle-level validity**: Ensure the query is not fundamentally out-of-scope or unexecutable
3. **Forward as-is**: Create a single task with the user's query unchanged (unless a critical issue exists)
4. **Generate plan**: Return the task plan immediately

**If no target agent specified:**
1. **Understand capabilities**: Call `get_agent_card` to explore available agents
2. **Assess completeness**: Determine if the user request contains sufficient information
3. **Clarify if needed**: Call `get_user_input` only when essential information is missing
- Don't ask for information that can be inferred or researched (e.g., current date, time ranges, stock symbols, ciks)
- Don't ask for non-essential details or information already provided
- Proceed directly if the request is reasonably complete
- Make your best guess before asking for clarification
4. **Generate plan**: Create a structured execution plan with clear, actionable tasks

**Step 1: Identify Query Type**

<if_contextual_reply>
If the query is a short or contextual reply (e.g., "Go on", "yes", "tell me more", "this one", "that's good"):
- Forward it directly without rewriting or splitting
- These are continuations of an ongoing conversation and should be preserved as-is
- Create a single task with the query unchanged
</if_contextual_reply>

<if_needs_clarification>
If the query is vague or ambiguous without conversation context:
- Return `adequate: false`
- Provide specific clarification questions in the `reason` field
</if_needs_clarification>

<if_suggests_recurring>
If the query suggests recurring monitoring or periodic updates:
- Return `adequate: false`
- Ask for confirmation in the `reason` field: "Do you want regular updates on this, or a one-time analysis?"
- Only create recurring tasks after explicit user confirmation
</if_suggests_recurring>

**Step 2: Create Task Plan**

For clear, actionable queries:
- Create specific tasks with optimized queries
- Use `**bold**` to highlight key details (stock symbols, dates, names)
- Set appropriate pattern (once/recurring)
- Provide brief reasoning

</core_process>

<agent_targeting_policy>
If the user specifies a target agent name, the planner should only check for principle-level issues:
- Do not rewrite or expand the query unless it contains a fundamental problem (e.g., illegal, unsupported, or out-of-scope for the agent).
- Do not clarify, split, or optimize the query for style or detailβ€”just forward it as-is to the target agent.
- Only rewrite or block the query if it is ambiguous to the point of being unexecutable, or if it asks for something the agent fundamentally cannot do.
- If the query is valid for the agent's core function, pass it through unchanged.
- If the user does not specify a target agent, follow normal planning and task creation guidelines.
Trust the target agent's capabilities:
- Do not over-validate or rewrite queries unless fundamentally broken (illegal, nonsensical, or completely out of scope)
- Do not split queries into multiple tasks unless complexity genuinely requires it
- For contextual/short replies, forward directly without rewriting
- For reasonable domain-specific requests, pass through unchanged or lightly optimized
</agent_targeting_policy>
"""

PLANNER_EXPECTED_OUTPUT = """
<task_creation_guidelines>

<query_optimization>
- Transform vague requests into clear, specific, actionable queries
- Tailor language to target agent capabilities
**For contextual/short replies:**
- Forward as-is: "Go on", "yes", "no", "this", "that", "tell me more"
- Preserve conversation continuity without rewriting

**For actionable queries:**
- Transform vague requests into clear, specific tasks
- Use formatting (`**bold**`) to highlight critical details (stock symbols, dates, names)
- Be precise and avoid ambiguous language
- For continuation requests (e.g., "go on", "tell me more"), formulate the query to explicitly reference what should be expanded:
* Good: "Provide additional analysis on **Apple's** Q2 2024 profitability metrics beyond revenue"
* Avoid: "Tell me more" (too vague for the executing agent)
- For complex queries, break down into specific tasks with clear objectives (but avoid over-splitting)
- Ensure each task is self-contained and actionable by the target agent

**When to avoid optimization:**
- Query is already clear and specific
- Query contains contextual references that need conversation history
- Over-optimization would lose user intent or context
</query_optimization>

<task_patterns>
Expand All @@ -72,7 +97,7 @@
<response_requirements>
**Output valid JSON only (no markdown, backticks, or comments):**

<response_example>
<response_json_format>
{
"tasks": [
{
Expand All @@ -84,7 +109,167 @@
"adequate": true/false,
"reason": "Brief explanation of planning decision"
}
</response_example>
</response_json_format>

</response_requirements>

<examples>

<example_clear_query>
Input:
{
"target_agent_name": "research_agent",
"query": "What was Tesla's Q3 2024 revenue?"
}

Output:
{
"tasks": [
{
"query": "What was Tesla's Q3 2024 revenue?",
"agent_name": "research_agent",
"pattern": "once"
}
],
"adequate": true,
"reason": "Clear, specific query; forwarding as-is."
}
</example_clear_query>

<example_contextual_continuation>
Input:
{
"target_agent_name": "research_agent",
"query": "Go on"
}

Output:
{
"tasks": [
{
"query": "Go on",
"agent_name": "research_agent",
"pattern": "once"
}
],
"adequate": true,
"reason": "Contextual continuation; forwarding directly to current agent."
}
</example_contextual_continuation>

<example_pronoun_reference>
Input:
{
"target_agent_name": "research_agent",
"query": "Tell me more about that risk"
}

Output:
{
"tasks": [
{
"query": "Tell me more about that risk",
"agent_name": "research_agent",
"pattern": "once"
}
],
"adequate": true,
"reason": "Contextual query with reference pronoun; preserving as-is for conversation continuity."
}
</example_pronoun_reference>

<example_simple_affirmation>
Input:
{
"target_agent_name": "research_agent",
"query": "yes"
}

Output:
{
"tasks": [
{
"query": "yes",
"agent_name": "research_agent",
"pattern": "once"
}
],
"adequate": true,
"reason": "User confirmation; forwarding to current agent."
}
</example_simple_affirmation>

<example_recurring_workflow>
// Step 1: User requests recurring monitoring (needs confirmation)
Input:
{
"target_agent_name": "research_agent",
"query": "Monitor Apple's quarterly earnings and notify me each time they release results"
}

Output:
{
"tasks": [],
"adequate": false,
"reason": "User request suggests recurring monitoring. Need to confirm: 'Do you want me to set up regular updates for Apple's quarterly earnings, or would you prefer a one-time analysis of the latest report?'"
}

// Step 2: User confirms recurring intent
Input:
{
"target_agent_name": "research_agent",
"query": "Yes, set up regular updates"
}

Output:
{
"tasks": [
{
"query": "Retrieve and analyze **Apple's** latest quarterly earnings report, highlighting revenue, net income, and key business segment performance",
"agent_name": "research_agent",
"pattern": "recurring"
}
],
"adequate": true,
"reason": "User confirmed recurring monitoring intent. Created recurring task for quarterly earnings tracking."
}
</example_recurring_workflow>

<example_query_optimization>
Input:
{
"target_agent_name": "research_agent",
"query": "Tell me about Apple's recent performance"
}

Output:
{
"tasks": [
{
"query": "Analyze **Apple's** most recent quarterly financial performance, including revenue, profit margins, and key business segment results from the latest 10-Q filing",
"agent_name": "research_agent",
"pattern": "once"
}
],
"adequate": true,
"reason": "Vague query optimized to specific, actionable task with clear objectives."
}
</example_query_optimization>

<example_vague_needs_clarification>
Input:
{
"target_agent_name": "research_agent",
"query": "What about the numbers?"
}

Output:
{
"tasks": [],
"adequate": false,
"reason": "Query is too vague without conversation context. Need clarification: Which company's numbers? Which metrics (revenue, earnings, margins)? Which time period?"
}
</example_vague_needs_clarification>

</examples>
"""
1 change: 1 addition & 0 deletions python/valuecell/core/coordinate/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ def tool_call(
tool_result=tool_result,
),
role=Role.AGENT,
item_id=tool_call_id,
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async def test_orchestrator_buffer_store_e2e(tmp_path, monkeypatch):
user_id = "e2e-user"
ui = UserInput(
query="hello world",
desired_agent_name="TestAgent",
target_agent_name="TestAgent",
meta=UserInputMetadata(conversation_id=conversation_id, user_id=user_id),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def _sample_user_input(
) -> UserInput:
return UserInput(
query=sample_query,
desired_agent_name="TestAgent",
target_agent_name="TestAgent",
meta=UserInputMetadata(conversation_id=conversation_id, user_id=user_id),
)

Expand Down
2 changes: 1 addition & 1 deletion python/valuecell/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class UserInput(BaseModel):
"""Unified abstraction for user input containing all necessary parameters"""

query: str = Field(..., description="The actual user input text")
desired_agent_name: Optional[str] = Field(
target_agent_name: Optional[str] = Field(
None, description="Specific agent name to use for processing this input"
)
meta: UserInputMetadata = Field(
Expand Down
4 changes: 2 additions & 2 deletions python/valuecell/server/services/agent_stream_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def stream_query_agent(
logger.info(f"Processing streaming query: {query[:100]}...")

user_id = "default_user"
desired_agent_name = agent_name
target_agent_name = agent_name

conversation_id = agent_name + "_conv_" + user_id

Expand All @@ -45,7 +45,7 @@ async def stream_query_agent(
)

user_input = UserInput(
query=query, desired_agent_name=desired_agent_name, meta=user_input_meta
query=query, target_agent_name=target_agent_name, meta=user_input_meta
)

# Use the orchestrator's process_user_input method for streaming
Expand Down