From 8504c4e52997b03e05153afe01b331bd1568d987 Mon Sep 17 00:00:00 2001
From: Zhaofeng Zhang <24791380+vcfgv@users.noreply.github.com>
Date: Fri, 17 Oct 2025 15:06:11 +0800
Subject: [PATCH 1/5] feat: add web search functionality and update tools in
ResearchAgent
---
.../valuecell/agents/research_agent/core.py | 20 ++++------
.../agents/research_agent/sources.py | 38 +++++++++++++++++++
2 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/python/valuecell/agents/research_agent/core.py b/python/valuecell/agents/research_agent/core.py
index a036b4421..e98dfa39b 100644
--- a/python/valuecell/agents/research_agent/core.py
+++ b/python/valuecell/agents/research_agent/core.py
@@ -16,6 +16,7 @@
from valuecell.agents.research_agent.sources import (
fetch_event_sec_filings,
fetch_periodic_sec_filings,
+ web_search,
)
from valuecell.agents.utils.context import build_ctx_from_dep
from valuecell.core.agent.responses import streaming
@@ -33,11 +34,16 @@ def _get_model_based_on_env():
class ResearchAgent(BaseAgent):
def __init__(self, **kwargs):
super().__init__(**kwargs)
+ tools = [
+ fetch_periodic_sec_filings,
+ fetch_event_sec_filings,
+ web_search,
+ ]
self.knowledge_research_agent = Agent(
model=_get_model_based_on_env(),
instructions=[KNOWLEDGE_AGENT_INSTRUCTION],
expected_output=KNOWLEDGE_AGENT_EXPECTED_OUTPUT,
- tools=[fetch_periodic_sec_filings, fetch_event_sec_filings],
+ tools=tools,
knowledge=knowledge,
db=InMemoryDb(),
# context
@@ -81,15 +87,3 @@ async def stream(
logger.info("Financial data analysis completed")
yield streaming.done()
-
-
-if __name__ == "__main__":
- import asyncio
-
- async def main():
- agent = ResearchAgent()
- query = "Provide a summary of Apple's 2024 all quarterly and annual reports."
- async for response in agent.stream(query, "test_session", "test_task"):
- print(response)
-
- asyncio.run(main())
diff --git a/python/valuecell/agents/research_agent/sources.py b/python/valuecell/agents/research_agent/sources.py
index a0b0dbf5b..ef08cc733 100644
--- a/python/valuecell/agents/research_agent/sources.py
+++ b/python/valuecell/agents/research_agent/sources.py
@@ -1,8 +1,12 @@
from datetime import date, datetime
+import os
from pathlib import Path
from typing import Iterable, List, Optional, Sequence
import aiofiles
+from agno.agent import Agent
+from agno.models.google import Gemini
+from agno.models.openrouter import OpenRouter
from edgar import Company
from edgar.entity.filings import EntityFilings
@@ -187,3 +191,37 @@ async def fetch_event_sec_filings(
filtered = filtered[:limit]
return await _write_and_ingest(filtered, Path(get_knowledge_path()))
+
+
+async def web_search(query: str) -> str:
+ """Search web for the given query and return a summary of the top results.
+
+ Args:
+ query: The search query string.
+
+ Returns:
+ A summary of the top search results.
+ """
+
+ if os.getenv("WEB_SEARCH_PROVIDER", "google").lower() == "google" and os.getenv(
+ "GOOGLE_API_KEY"
+ ):
+ return _web_search_google(query)
+
+ model = OpenRouter(id="perplexity/sonar", max_tokens=None)
+ response = await Agent(model=model).arun(query)
+ return response.content
+
+
+async def _web_search_google(query: str) -> str:
+ """Search Google for the given query and return a summary of the top results.
+
+ Args:
+ query: The search query string.
+
+ Returns:
+ A summary of the top search results.
+ """
+ model = Gemini(id="gemini-2.5-flash", search=True)
+ response = await Agent(model=model).arun(query)
+ return response.content
From 3a83a1e03015462365a07d64d704f6af8944f79a Mon Sep 17 00:00:00 2001
From: Zhaofeng Zhang <24791380+vcfgv@users.noreply.github.com>
Date: Fri, 17 Oct 2025 15:37:52 +0800
Subject: [PATCH 2/5] feat: add user preference handling for conversation
policies in planner prompts
---
.../core/coordinate/planner_prompts.py | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/python/valuecell/core/coordinate/planner_prompts.py b/python/valuecell/core/coordinate/planner_prompts.py
index ae5bd8094..39536255a 100644
--- a/python/valuecell/core/coordinate/planner_prompts.py
+++ b/python/valuecell/core/coordinate/planner_prompts.py
@@ -29,6 +29,13 @@
- Provide specific clarification questions in the `reason` field
+
+If the user expresses a normative preference or rule (e.g., "do not provide investment advice", "always summarize in bullet points"):
+- Treat this as a user preference or conversation policy, not an executable task.
+- Do NOT create tasks for these statements. Instead, record/update the preference in the conversation or user settings metadata.
+- Respond with no tasks (empty `tasks` array) and set `adequate: true` with reason indicating the preference was recorded, or `adequate: false` only if clarification is needed.
+
+
If the query suggests recurring monitoring or periodic updates:
- Return `adequate: false`
@@ -157,6 +164,27 @@
}
+
+Input:
+{
+ "target_agent_name": "research_agent",
+ "query": "Do not provide investment advice; summarize only factual data"
+}
+
+Output:
+{
+ "tasks": [
+ {
+ "query": "Do not provide investment advice; summarize only factual data",
+ "agent_name": "research_agent",
+ "pattern": "once"
+ }
+ ],
+ "adequate": true,
+ "reason": "User preference recorded: do not provide investment advice; future outputs should be factual-only summaries."
+}
+
+
Input:
{
From cf542ec375b40805b63bbee8cc5026864062aa73 Mon Sep 17 00:00:00 2001
From: Zhaofeng Zhang <24791380+vcfgv@users.noreply.github.com>
Date: Fri, 17 Oct 2025 17:54:41 +0800
Subject: [PATCH 3/5] feat: refactor model initialization and improve web
search handling
---
python/valuecell/agents/research_agent/core.py | 12 ++----------
python/valuecell/agents/research_agent/sources.py | 2 +-
python/valuecell/core/coordinate/planner.py | 8 ++------
python/valuecell/utils/model.py | 11 +++++++++++
4 files changed, 16 insertions(+), 17 deletions(-)
create mode 100644 python/valuecell/utils/model.py
diff --git a/python/valuecell/agents/research_agent/core.py b/python/valuecell/agents/research_agent/core.py
index e98dfa39b..205917e39 100644
--- a/python/valuecell/agents/research_agent/core.py
+++ b/python/valuecell/agents/research_agent/core.py
@@ -3,8 +3,6 @@
from agno.agent import Agent
from agno.db.in_memory import InMemoryDb
-from agno.models.google import Gemini
-from agno.models.openrouter import OpenRouter
from edgar import set_identity
from loguru import logger
@@ -22,13 +20,7 @@
from valuecell.core.agent.responses import streaming
from valuecell.core.types import BaseAgent, StreamResponse
from valuecell.utils.env import agent_debug_mode_enabled
-
-
-def _get_model_based_on_env():
- model_id = os.getenv("RESEARCH_AGENT_MODEL_ID")
- if os.getenv("GOOGLE_API_KEY"):
- return Gemini(id=model_id or "gemini-2.5-flash")
- return OpenRouter(id=model_id or "google/gemini-2.5-flash", max_tokens=None)
+from valuecell.utils.model import get_model
class ResearchAgent(BaseAgent):
@@ -40,7 +32,7 @@ def __init__(self, **kwargs):
web_search,
]
self.knowledge_research_agent = Agent(
- model=_get_model_based_on_env(),
+ model=get_model("RESEARCH_AGENT_MODEL_ID"),
instructions=[KNOWLEDGE_AGENT_INSTRUCTION],
expected_output=KNOWLEDGE_AGENT_EXPECTED_OUTPUT,
tools=tools,
diff --git a/python/valuecell/agents/research_agent/sources.py b/python/valuecell/agents/research_agent/sources.py
index ef08cc733..0c5224557 100644
--- a/python/valuecell/agents/research_agent/sources.py
+++ b/python/valuecell/agents/research_agent/sources.py
@@ -206,7 +206,7 @@ async def web_search(query: str) -> str:
if os.getenv("WEB_SEARCH_PROVIDER", "google").lower() == "google" and os.getenv(
"GOOGLE_API_KEY"
):
- return _web_search_google(query)
+ return await _web_search_google(query)
model = OpenRouter(id="perplexity/sonar", max_tokens=None)
response = await Agent(model=model).arun(query)
diff --git a/python/valuecell/core/coordinate/planner.py b/python/valuecell/core/coordinate/planner.py
index eca84a19b..119af5261 100644
--- a/python/valuecell/core/coordinate/planner.py
+++ b/python/valuecell/core/coordinate/planner.py
@@ -12,14 +12,12 @@
import asyncio
import logging
-import os
from datetime import datetime
from typing import Callable, List, Optional
from a2a.types import AgentCard
from agno.agent import Agent
from agno.db.in_memory import InMemoryDb
-from agno.models.openrouter import OpenRouter
from valuecell.core.agent.connect import RemoteConnections
from valuecell.core.coordinate.planner_prompts import (
@@ -30,6 +28,7 @@
from valuecell.core.types import UserInput
from valuecell.utils import generate_uuid
from valuecell.utils.env import agent_debug_mode_enabled
+from valuecell.utils.model import get_model
from .models import ExecutionPlan, PlannerInput, PlannerResponse
@@ -152,10 +151,7 @@ async def _analyze_input_and_create_tasks(
"""
# Create planning agent with appropriate tools and instructions
agent = Agent(
- model=OpenRouter(
- id=os.getenv("PLANNER_MODEL_ID", "google/gemini-2.5-flash"),
- max_tokens=None,
- ),
+ model=get_model("PLANNER_MODEL_ID"),
tools=[
# TODO: enable UserControlFlowTools when stable
# UserControlFlowTools(),
diff --git a/python/valuecell/utils/model.py b/python/valuecell/utils/model.py
new file mode 100644
index 000000000..ff82bc6fa
--- /dev/null
+++ b/python/valuecell/utils/model.py
@@ -0,0 +1,11 @@
+import os
+
+from agno.models.google import Gemini
+from agno.models.openrouter import OpenRouter
+
+
+def get_model(env_key: str):
+ model_id = os.getenv(env_key)
+ if os.getenv("GOOGLE_API_KEY"):
+ return Gemini(id=model_id or "gemini-2.5-flash")
+ return OpenRouter(id=model_id or "google/gemini-2.5-flash", max_tokens=None)
From 6c95d89fd348557005051f276d0b1c850b455012 Mon Sep 17 00:00:00 2001
From: Zhaofeng Zhang <24791380+vcfgv@users.noreply.github.com>
Date: Fri, 17 Oct 2025 17:54:49 +0800
Subject: [PATCH 4/5] feat: update planner instructions to clarify task
handling and user query processing
---
.../core/coordinate/planner_prompts.py | 230 +++---------------
1 file changed, 40 insertions(+), 190 deletions(-)
diff --git a/python/valuecell/core/coordinate/planner_prompts.py b/python/valuecell/core/coordinate/planner_prompts.py
index 39536255a..4d7fbb8c2 100644
--- a/python/valuecell/core/coordinate/planner_prompts.py
+++ b/python/valuecell/core/coordinate/planner_prompts.py
@@ -9,95 +9,44 @@
# noqa: E501
PLANNER_INSTRUCTION = """
-You are an AI Agent execution planner that analyzes user requests and creates executable task plans using available agents.
+You are an AI Agent execution planner that forwards user requests to the specified target agent as simple, executable tasks.
-
+
+1) Default pass-through
+- Assume `target_agent_name` is always provided.
+- Create exactly one task with the user's query unchanged.
+- Set `pattern` to `once` by default.
-**Step 1: Identify Query Type**
+2) Avoid optimization
+- Do NOT rewrite, optimize, summarize, or split the query.
+- Only block when the request is clearly unusable (e.g., illegal content or impossible instruction). In that case, return `adequate: false` with a short reason and no tasks.
-
-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
-
+3) Contextual and preference statements
+- Treat short/contextual replies (e.g., "Go on", "yes", "tell me more") and user preferences/rules (e.g., "do not provide investment advice") as valid inputs; forward them unchanged as a single task.
-
-If the query is vague or ambiguous without conversation context:
-- Return `adequate: false`
-- Provide specific clarification questions in the `reason` field
-
+4) Recurring intent confirmation
+- If the query suggests recurring monitoring or periodic updates, DO NOT create tasks yet. Return `adequate: false` and ask for confirmation in `reason` (e.g., "Do you want regular updates on this, or a one-time analysis?").
+- After explicit confirmation, create a single task with `pattern: recurring` and keep the original query unchanged.
-
-If the user expresses a normative preference or rule (e.g., "do not provide investment advice", "always summarize in bullet points"):
-- Treat this as a user preference or conversation policy, not an executable task.
-- Do NOT create tasks for these statements. Instead, record/update the preference in the conversation or user settings metadata.
-- Respond with no tasks (empty `tasks` array) and set `adequate: true` with reason indicating the preference was recorded, or `adequate: false` only if clarification is needed.
-
-
-
-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
-
-
-**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
-
-
-
-
-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
-
+5) Agent targeting policy
+- Trust the specified agent's capabilities; do not over-validate or split into multiple tasks.
+
"""
PLANNER_EXPECTED_OUTPUT = """
-
-**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 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
+
+- Default to pass-through: create a single task addressed to the provided `target_agent_name` with the user's query unchanged.
+- Set `pattern` to `once` unless the user explicitly confirms recurring intent.
+- Avoid query optimization and task splitting.
+
-**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
-
-
-
-- **ONCE**: Single execution with immediate results (default)
-- **RECURRING**: Periodic execution for ongoing monitoring/updates
- - Use only when user explicitly requests regular updates
- - Always confirm intent before creating recurring tasks: "Do you want regular updates on this?"
-
-
-
-- If user specifies a target agent name, do not split user query into multiple tasks; create a single task for the specified agent.
-- Avoid splitting tasks into excessively fine-grained steps. Tasks should be actionable by the target agent without requiring manual orchestration of many micro-steps.
-- Aim for a small set of clear tasks (typical target: 1–5 tasks) for straightforward requests. For complex research, group related micro-steps under a single task with an internal subtask description.
-- Do NOT create separate tasks for trivial UI interactions or internal implementation details (e.g., "open page", "click button"). Instead, express the goal the agent should achieve (e.g., "Retrieve Q4 2024 revenue from the 10-Q and cite the filing").
-- When a user requests very deep or multi-stage research, it's acceptable to create a short sequence (e.g., 3–8 tasks) but prefer grouping and clear handoffs.
-- If unsure about granularity, prefer slightly larger tasks and include explicit guidance in the task's query about intermediate checks or tolerances.
-
+
+- If the request is clearly unusable (illegal content or impossible instruction), return `adequate: false` with a short reason and no tasks.
+- If the request suggests recurring monitoring, return `adequate: false` with a confirmation question; after explicit confirmation, create a single `recurring` task with the original query unchanged.
+
@@ -108,7 +57,7 @@
{
"tasks": [
{
- "query": "Clear, specific task description with **key details** highlighted",
+ "query": "User's original query, unchanged",
"agent_name": "target_agent_name",
"pattern": "once" | "recurring"
}
@@ -122,7 +71,7 @@
-
+
Input:
{
"target_agent_name": "research_agent",
@@ -139,11 +88,11 @@
}
],
"adequate": true,
- "reason": "Clear, specific query; forwarding as-is."
+ "reason": "Pass-through to the specified agent."
}
-
+
-
+
Input:
{
"target_agent_name": "research_agent",
@@ -160,75 +109,12 @@
}
],
"adequate": true,
- "reason": "Contextual continuation; forwarding directly to current agent."
-}
-
-
-
-Input:
-{
- "target_agent_name": "research_agent",
- "query": "Do not provide investment advice; summarize only factual data"
-}
-
-Output:
-{
- "tasks": [
- {
- "query": "Do not provide investment advice; summarize only factual data",
- "agent_name": "research_agent",
- "pattern": "once"
- }
- ],
- "adequate": true,
- "reason": "User preference recorded: do not provide investment advice; future outputs should be factual-only summaries."
+ "reason": "Contextual continuation; forwarded unchanged."
}
-
+
-
-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."
-}
-
-
-
-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."
-}
-
-
-
-// Step 1: User requests recurring monitoring (needs confirmation)
+
+// Step 1: needs confirmation
Input:
{
"target_agent_name": "research_agent",
@@ -239,10 +125,10 @@
{
"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?'"
+ "reason": "This suggests recurring monitoring. Do you want regular updates on this, or a one-time analysis?"
}
-// Step 2: User confirms recurring intent
+// Step 2: user confirms
Input:
{
"target_agent_name": "research_agent",
@@ -253,51 +139,15 @@
{
"tasks": [
{
- "query": "Retrieve and analyze **Apple's** latest quarterly earnings report, highlighting revenue, net income, and key business segment performance",
+ "query": "Yes, set up regular updates",
"agent_name": "research_agent",
"pattern": "recurring"
}
],
"adequate": true,
- "reason": "User confirmed recurring monitoring intent. Created recurring task for quarterly earnings tracking."
-}
-
-
-
-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."
-}
-
-
-
-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?"
+ "reason": "User confirmed recurring intent; created a single recurring task with the original query."
}
-
+
"""
From c350ef039cd08a17793adb38a9be19b7a9dcacb7 Mon Sep 17 00:00:00 2001
From: Zhaofeng Zhang <24791380+vcfgv@users.noreply.github.com>
Date: Fri, 17 Oct 2025 18:07:13 +0800
Subject: [PATCH 5/5] fix: reorder import statements for consistency
---
python/valuecell/agents/research_agent/sources.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/valuecell/agents/research_agent/sources.py b/python/valuecell/agents/research_agent/sources.py
index 0c5224557..50f4869f6 100644
--- a/python/valuecell/agents/research_agent/sources.py
+++ b/python/valuecell/agents/research_agent/sources.py
@@ -1,5 +1,5 @@
-from datetime import date, datetime
import os
+from datetime import date, datetime
from pathlib import Path
from typing import Iterable, List, Optional, Sequence