diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2d03519..f4b8bd3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,20 +1,20 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v2.3.0
+ rev: v5.0.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
- rev: 22.10.0
+ rev: 25.1.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
- rev: 7.2.0
+ rev: 7.3.0
hooks:
- id: flake8
- repo: https://github.com/pycqa/isort
- rev: 5.12.0
+ rev: 6.0.1
hooks:
- id: isort
args: [--profile=black]
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..0fc7269
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,103 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Askademic is a CLI tool that helps users find information in research papers from arXiv. It's built on PydanticAI and provides:
+- Summarization of latest papers in categories
+- Question answering by searching relevant papers
+- Specific paper retrieval by title/link/arXiv ID
+
+## Development Commands
+
+### Installation & Setup
+```bash
+pip install . # Install the package
+pip install -e . # Install in development mode
+```
+
+### Testing
+```bash
+pytest # Run all tests
+pytest tests/test_article.py # Run specific test file
+python evals/evals.py # Run evaluation tests
+```
+
+### Running the Application
+```bash
+askademic # Start the CLI application
+```
+
+### Code Quality
+```bash
+flake8 # Lint code (max line length: 99)
+pre-commit run --all-files # Run pre-commit hooks
+```
+
+## Architecture
+
+### Core Components
+
+1. **main.py**: Entry point with CLI interface using prompt_toolkit and rich console
+2. **orchestrator.py**: Main coordination agent that routes requests to specialized agents
+3. **allower.py**: Determines if user queries are scientific (routes to orchestrator or returns puns)
+4. **Specialized Agents**:
+ - **summary.py**: SummaryAgent for latest paper summaries
+ - **question.py**: QuestionAgent for Q&A with arXiv search
+ - **article.py**: ArticleAgent for specific paper retrieval and analysis
+5. **memory.py**: Manages conversation context and token limits
+6. **utils.py**: Model selection and arXiv API utilities
+7. **tools.py**: Utility functions for arXiv interactions
+
+### Agent Architecture
+- Built on PydanticAI with structured Pydantic model outputs
+- Uses orchestrator pattern - main orchestrator routes to specialized agents
+- Each agent has specific tools and capabilities
+- Memory management tracks conversation history with token limits
+
+### Model Support
+- **Gemini 2.0 Flash** (default, preferred for cost and context window)
+- **Claude 3.5 Haiku** (experimental, rate limited)
+- **Claude via AWS Bedrock** (experimental)
+- **Nova Pro via AWS Bedrock** (experimental)
+
+## Configuration
+
+### Environment Variables
+Required in `.env` file (copy from `.env-template`):
+- `LLM_FAMILY`: "gemini", "claude", "claude-aws-bedrock", or "nova-pro-aws-bedrock"
+- `GEMINI_API_KEY`: For Gemini models
+- `ANTHROPIC_API_KEY`: For Claude models
+- `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`: For AWS Bedrock
+
+### Key Settings
+- Max request tokens: 100,000 (configured in memory.py)
+- Usage limits: 20 requests per agent run
+- Default temperature: 0 for consistency
+- Retry strategy: 20 retries with early termination
+
+## Development Guidelines
+
+### File Structure
+- `src/askademic/`: Main package
+- `prompts/`: System prompts in separate files
+- `tests/`: Unit tests for each component
+- `evals/`: Evaluation scripts
+- `logs/`: Daily log files (auto-generated)
+
+### Testing Strategy
+- Unit tests for each agent and utility
+- Manual evaluation suite in `evals/` directory
+- CI/CD with GitHub Actions testing Python 3.11-3.13
+
+### Agent Development
+- Each agent follows PydanticAI patterns with structured outputs
+- Tools decorated with `@agent.tool` for function calling
+- Async/await throughout for API calls
+- Comprehensive logging to daily log files
+
+### Memory Management
+- Conversation history maintained with token counting
+- Automatic cleanup when limits exceeded
+- Context window management for different models
diff --git a/README.md b/README.md
index c15da96..b18566f 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ Askademic is an AI agent, working as a CLI tool, that helps you with finding inf
* summarise the latest papers in a category
* answer questions, searching first for relevant papers
* retrieve info about a specific paper, by link or title
+* handle flexible academic requests that don't fit the standard categories
You can also ask follow-up questions. And, it has an eye for things non-scientific... see below.
diff --git a/evals/evals.py b/evals/evals.py
index d72e201..5d60a18 100644
--- a/evals/evals.py
+++ b/evals/evals.py
@@ -5,6 +5,7 @@
from dotenv import load_dotenv
from evals_allower import run_evals as run_evals_allower
from evals_article import run_evals as run_evals_article
+from evals_general import run_evals as run_evals_general
from evals_orchestrator import run_evals as run_evals_orchestrator
from evals_question import run_evals as run_evals_question
from evals_summary import run_evals as run_evals_summary
@@ -67,6 +68,9 @@ async def main():
console.print("\n[bold magenta]Running article evals...[/bold magenta]")
await run_evals_article(model_family)
+ console.print("\n[bold magenta]Running general agent evals...[/bold magenta]")
+ await run_evals_general(model_family)
+
if __name__ == "__main__":
asyncio.run(main())
diff --git a/evals/evals_general.py b/evals/evals_general.py
new file mode 100644
index 0000000..1f4915e
--- /dev/null
+++ b/evals/evals_general.py
@@ -0,0 +1,305 @@
+"""
+Evaluations for the general academic agent.
+Tests flexibility, adaptability, and handling of diverse academic requests.
+"""
+
+import time
+from typing import List
+
+from rich.console import Console
+
+from askademic.general import GeneralAgent
+from askademic.utils import choose_model
+
+
+class GeneralTestCase:
+ def __init__(
+ self,
+ request: str,
+ expected_keywords: List[str],
+ description: str,
+ min_confidence_threshold: str = "low",
+ ):
+ self.request = request
+ self.expected_keywords = (
+ expected_keywords # Keywords that should appear in response
+ )
+ self.description = description
+ self.min_confidence_threshold = min_confidence_threshold
+
+
+class FlexibilityTestCase:
+ def __init__(self, request: str, should_handle: bool, description: str):
+ self.request = request
+ self.should_handle = (
+ should_handle # Whether agent should successfully handle this
+ )
+ self.description = description
+
+
+# Test cases for different types of academic requests
+keyword_eval_cases = [
+ GeneralTestCase(
+ "How do I design a good research methodology for machine learning experiments?",
+ ["methodology", "experiment", "design", "research", "machine learning"],
+ "Research methodology guidance",
+ "medium",
+ ),
+ GeneralTestCase(
+ "What are the key principles of academic writing?",
+ ["writing", "academic", "principles", "structure"],
+ "Academic writing guidance",
+ "high",
+ ),
+ GeneralTestCase(
+ "Explain the concept of statistical significance in simple terms",
+ ["statistical", "significance", "p-value", "hypothesis", "test"],
+ "Concept explanation",
+ "medium",
+ ),
+ GeneralTestCase(
+ "What's the difference between quantitative and qualitative research methods?",
+ ["quantitative", "qualitative", "research", "methods", "data"],
+ "Methodological comparison",
+ "high",
+ ),
+ GeneralTestCase(
+ "How should I structure a literature review for my thesis?",
+ ["literature", "review", "structure", "thesis", "sources"],
+ "Academic guidance",
+ "medium",
+ ),
+ GeneralTestCase(
+ "What are some interdisciplinary approaches to studying climate change?",
+ ["interdisciplinary", "climate", "approaches", "research"],
+ "Interdisciplinary research",
+ "medium",
+ ),
+ GeneralTestCase(
+ "How do I choose the right statistical test for my data?",
+ ["statistical", "test", "data", "analysis", "choose"],
+ "Statistical guidance",
+ "medium",
+ ),
+ GeneralTestCase(
+ "What are the ethical considerations in AI research?",
+ ["ethical", "AI", "research", "considerations", "bias"],
+ "Ethics in research",
+ "medium",
+ ),
+]
+
+# Test cases for flexibility and edge case handling
+flexibility_eval_cases = [
+ FlexibilityTestCase(
+ "I'm confused about which research paradigm to use for my sociology study",
+ True,
+ "Research paradigm guidance",
+ ),
+ FlexibilityTestCase(
+ "Can you help me understand the peer review process?",
+ True,
+ "Academic process explanation",
+ ),
+ FlexibilityTestCase(
+ "What are the current debates in computational linguistics?",
+ True,
+ "Field overview request",
+ ),
+ FlexibilityTestCase(
+ "How do I deal with contradictory findings in my literature review?",
+ True,
+ "Academic problem-solving",
+ ),
+ FlexibilityTestCase(
+ "What's the best way to present negative results in a research paper?",
+ True,
+ "Academic communication",
+ ),
+ FlexibilityTestCase(
+ "I need help understanding Bayesian vs frequentist statistics",
+ True,
+ "Conceptual comparison",
+ ),
+ FlexibilityTestCase(
+ "How do I write a compelling research proposal?",
+ True,
+ "Academic writing guidance",
+ ),
+ FlexibilityTestCase(
+ "What are the emerging trends in data visualization for scientific papers?",
+ True,
+ "Current trends inquiry",
+ ),
+]
+
+console = Console()
+MAX_ATTEMPTS = 3
+
+
+async def run_keyword_evals(model_family: str):
+ """Test if general agent responses contain expected keywords"""
+
+ model, model_settings = choose_model(model_family)
+ general_agent = GeneralAgent(model, model_settings)
+
+ c_passed, c_failed = 0, 0
+
+ for case in keyword_eval_cases:
+ time.sleep(2)
+ attempt = 0
+
+ while attempt < MAX_ATTEMPTS:
+ try:
+ console.print(f"[dim]Evaluating: {case.description}[/dim]")
+ console.print(f"[dim]Request: {case.request[:60]}...[/dim]")
+
+ response = await general_agent(case.request)
+
+ # Check if response contains expected keywords
+ response_text = response.response.lower()
+ found_keywords = []
+ missing_keywords = []
+
+ for keyword in case.expected_keywords:
+ if keyword.lower() in response_text:
+ found_keywords.append(keyword)
+ else:
+ missing_keywords.append(keyword)
+
+ # Pass if at least 60% of keywords are found
+ keyword_score = len(found_keywords) / len(case.expected_keywords)
+
+ if keyword_score >= 0.6:
+ console.print(
+ f"[green]✓ PASS[/green] - Found {len(found_keywords)}/{len(case.expected_keywords)} keywords"
+ )
+ c_passed += 1
+ else:
+ console.print(
+ f"[red]✗ FAIL[/red] - Found {len(found_keywords)}/{len(case.expected_keywords)} keywords"
+ )
+ console.print(f"[dim]Missing: {missing_keywords}[/dim]")
+ c_failed += 1
+
+ break
+
+ except Exception as e:
+ console.print(f"[yellow]Error: {e}[/yellow]")
+ attempt += 1
+ time.sleep(10)
+
+ if attempt == MAX_ATTEMPTS:
+ console.print(
+ f"[red]Max attempts reached for: {case.description}[/red]"
+ )
+ c_failed += 1
+
+ return c_passed, c_failed
+
+
+async def run_flexibility_evals(model_family: str):
+ """Test if general agent can handle diverse request types"""
+
+ model, model_settings = choose_model(model_family)
+ general_agent = GeneralAgent(model, model_settings)
+
+ c_passed, c_failed = 0, 0
+
+ for case in flexibility_eval_cases:
+ time.sleep(2)
+ attempt = 0
+
+ while attempt < MAX_ATTEMPTS:
+ try:
+ console.print(f"[dim]Evaluating: {case.description}[/dim]")
+ console.print(f"[dim]Request: {case.request[:60]}...[/dim]")
+
+ response = await general_agent(case.request)
+
+ # Check if agent provided a substantive response
+ response_length = len(response.response.strip())
+ has_sources = len(response.sources_used) > 0
+ has_followup = len(response.suggested_followup) > 0
+
+ # Pass if response is substantive (>100 chars) and shows engagement
+ is_substantive = response_length > 100
+
+ if case.should_handle:
+ if is_substantive:
+ console.print(
+ f"[green]✓ PASS[/green] - Substantive response ({response_length} chars)"
+ )
+ if has_sources:
+ console.print(
+ f"[dim]+ Used {len(response.sources_used)} sources[/dim]"
+ )
+ if has_followup:
+ console.print(
+ f"[dim]+ Provided {len(response.suggested_followup)} follow-ups[/dim]"
+ )
+ c_passed += 1
+ else:
+ console.print(
+ f"[red]✗ FAIL[/red] - Response too brief ({response_length} chars)"
+ )
+ c_failed += 1
+ else:
+ # For cases that should not be handled well
+ if not is_substantive:
+ console.print(
+ "[green]✓ PASS[/green] - Appropriately brief response"
+ )
+ c_passed += 1
+ else:
+ console.print(
+ "[red]✗ FAIL[/red] - Should not have handled this well"
+ )
+ c_failed += 1
+
+ break
+
+ except Exception as e:
+ console.print(f"[yellow]Error: {e}[/yellow]")
+ attempt += 1
+ time.sleep(10)
+
+ if attempt == MAX_ATTEMPTS:
+ console.print(
+ f"[red]Max attempts reached for: {case.description}[/red]"
+ )
+ c_failed += 1
+
+ return c_passed, c_failed
+
+
+async def run_evals(model_family: str):
+ """Run all general agent evaluations"""
+
+ console.print(
+ f"[bold blue]Running General Agent Evaluations for {model_family}[/bold blue]"
+ )
+
+ # Run keyword evaluations
+ console.print("\n[bold cyan]Testing keyword relevance...[/bold cyan]")
+ keyword_passed, keyword_failed = await run_keyword_evals(model_family)
+
+ # Run flexibility evaluations
+ console.print("\n[bold cyan]Testing flexibility and adaptability...[/bold cyan]")
+ flex_passed, flex_failed = await run_flexibility_evals(model_family)
+
+ # Summary
+ total_passed = keyword_passed + flex_passed
+ total_failed = keyword_failed + flex_failed
+ total_cases = len(keyword_eval_cases) + len(flexibility_eval_cases)
+
+ console.print("\n[bold magenta]General Agent Evaluation Summary[/bold magenta]")
+ console.print(f"[bold cyan]Total cases: {total_cases}[/bold cyan]")
+ console.print(f"[green]✓ Passed: {total_passed}[/green]")
+
+ if total_failed > 0:
+ console.print(f"[red]✗ Failed: {total_failed}[/red]")
+ success_rate = (total_passed / total_cases) * 100
+ console.print(f"[yellow]Success rate: {success_rate:.1f}%[/yellow]")
+ else:
+ console.print("[bold green]All tests passed! 🎉[/bold green]")
diff --git a/evals/evals_orchestrator.py b/evals/evals_orchestrator.py
index 9e86fdb..1e21445 100644
--- a/evals/evals_orchestrator.py
+++ b/evals/evals_orchestrator.py
@@ -8,6 +8,7 @@
from rich.console import Console
from askademic.article import ArticleResponse
+from askademic.general import GeneralResponse
from askademic.orchestrator import orchestrator_agent_base
from askademic.question import QuestionAnswerResponse
from askademic.summary import SummaryResponse
@@ -18,24 +19,46 @@ class OrchestratorTestCase:
def __init__(
self,
request: str,
- response_type: SummaryResponse | QuestionAnswerResponse | ArticleResponse,
+ response_type: (
+ SummaryResponse | QuestionAnswerResponse | ArticleResponse | GeneralResponse
+ ),
):
self.request = request
self.response_type = response_type
eval_cases = [
+ # Summary routing tests
OrchestratorTestCase(
"What is the latest research on quantum computing?", SummaryResponse
),
OrchestratorTestCase("Can you summarize the latest papers on AI?", SummaryResponse),
+ # Question answering routing tests
OrchestratorTestCase(
"What's the relation between context length and quality in LLM performance?",
QuestionAnswerResponse,
),
+ # Article routing tests
OrchestratorTestCase(
"Tell me all about the paper 'Attention is all you need'", ArticleResponse
),
+ # General academic routing tests - these should route to general_academic
+ OrchestratorTestCase(
+ "How do I design a good research methodology?", GeneralResponse
+ ),
+ OrchestratorTestCase(
+ "What are the key principles of academic writing?", GeneralResponse
+ ),
+ OrchestratorTestCase(
+ "Explain the concept of statistical significance", GeneralResponse
+ ),
+ OrchestratorTestCase(
+ "What's the difference between quantitative and qualitative research?",
+ GeneralResponse,
+ ),
+ OrchestratorTestCase(
+ "How should I structure a literature review?", GeneralResponse
+ ),
]
console = Console()
diff --git a/setup.cfg b/setup.cfg
index 61d9081..6deafc2 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,2 @@
[flake8]
-max-line-length = 99
+max-line-length = 120
diff --git a/src/askademic/general.py b/src/askademic/general.py
new file mode 100644
index 0000000..042a16b
--- /dev/null
+++ b/src/askademic/general.py
@@ -0,0 +1,126 @@
+import logging
+from datetime import datetime
+from typing import List
+
+from pydantic import BaseModel, Field
+from pydantic_ai import Agent, RunContext
+from pydantic_ai.models import Model
+from pydantic_ai.settings import ModelSettings
+
+from askademic.prompts.general import SYSTEM_PROMPT_GENERAL
+from askademic.tools import (
+ get_article,
+ get_categories,
+ search_articles_by_abs,
+ search_articles_by_title,
+)
+
+today = datetime.now().strftime("%Y-%m-%d")
+logging.basicConfig(level=logging.INFO, filename=f"logs/{today}_logs.txt")
+logger = logging.getLogger(__name__)
+
+
+class GeneralResponse(BaseModel):
+ """The response for general academic requests that don't fit standard categories."""
+
+ response: str = Field(description="The response to the academic request")
+ sources_used: List[str] = Field(
+ description="List of article links or sources used in the response", default=[]
+ )
+ suggested_followup: List[str] = Field(
+ description="Suggested follow-up questions or actions", default=[]
+ )
+ confidence: str = Field(
+ description="Confidence level: high, medium, or low", default="medium"
+ )
+
+
+general_agent_base = Agent(
+ system_prompt=SYSTEM_PROMPT_GENERAL,
+ output_type=GeneralResponse,
+ retries=20,
+ end_strategy="early",
+)
+
+
+class Context(BaseModel):
+ pass
+
+
+@general_agent_base.tool
+async def search_papers_by_topic(
+ ctx: RunContext[Context], topic: str, max_results: int = 10
+) -> str:
+ """
+ Search for papers related to a topic by searching abstracts.
+ Args:
+ ctx: the context
+ topic: The research topic or keywords to search for
+ max_results: Maximum number of results to return
+ """
+ logger.info(f"{datetime.now()}: General agent searching for topic: {topic}")
+ result = search_articles_by_abs(query=topic, max_results=max_results)
+ return result
+
+
+@general_agent_base.tool
+async def search_papers_by_title_keyword(
+ ctx: RunContext[Context], title_keywords: str, max_results: int = 10
+) -> str:
+ """
+ Search for papers by title keywords.
+ Args:
+ ctx: the context
+ title_keywords: Keywords that might appear in paper titles
+ max_results: Maximum number of results to return
+ """
+ logger.info(
+ f"{datetime.now()}: General agent searching titles for: {title_keywords}"
+ )
+ result = search_articles_by_title(query=title_keywords, max_results=max_results)
+ return result
+
+
+@general_agent_base.tool
+async def get_paper_content(ctx: RunContext[Context], paper_url: str) -> str:
+ """
+ Retrieve the full content of a specific paper.
+ Args:
+ ctx: the context
+ paper_url: The arXiv PDF URL of the paper
+ """
+ logger.info(f"{datetime.now()}: General agent retrieving paper: {paper_url}")
+ result = get_article(url=paper_url)
+ return result
+
+
+@general_agent_base.tool
+async def list_research_categories(ctx: RunContext[Context]) -> dict:
+ """
+ Get all available arXiv research categories.
+ Args:
+ ctx: the context
+ """
+ logger.info(f"{datetime.now()}: General agent listing categories")
+ return get_categories()
+
+
+class GeneralAgent:
+ def __init__(self, model: Model, model_settings: ModelSettings = None):
+ self.agent = general_agent_base
+ self.agent.model = model
+ if model_settings:
+ self.agent.model_settings = model_settings
+
+ async def __call__(self, request: str) -> GeneralResponse:
+ """
+ Handle a general academic request.
+ Args:
+ request: The user's academic request
+ """
+ logger.info(
+ f"{datetime.now()}: General agent handling request: {request[:100]}..."
+ )
+
+ result = await self.agent.run(request)
+ return result.output
diff --git a/src/askademic/main.py b/src/askademic/main.py
index fd50baa..899b386 100644
--- a/src/askademic/main.py
+++ b/src/askademic/main.py
@@ -105,6 +105,7 @@ async def ask_me():
in an arXiv category or subcategory,
- find answers for specific research questions/topics
- retrieve a specific paper by title or arXiv URL
+ - handle flexible academic requests that don't fit the standard categories
Ask me a question with either of these requests.
I will do the heavy lifting for you, you can ask follow-up questions too.
diff --git a/src/askademic/orchestrator.py b/src/askademic/orchestrator.py
index 9f52161..aec6bc2 100644
--- a/src/askademic/orchestrator.py
+++ b/src/askademic/orchestrator.py
@@ -5,6 +5,7 @@
from pydantic_ai import Agent, RunContext
from askademic.article import ArticleAgent, ArticleResponse
+from askademic.general import GeneralAgent, GeneralResponse
from askademic.prompts.general import SYSTEM_PROMPT_ORCHESTRATOR
from askademic.question import QuestionAgent, QuestionAnswerResponse
from askademic.summary import SummaryAgent, SummaryResponse
@@ -21,15 +22,17 @@ class Context(BaseModel):
class OrchestratorResponse(BaseModel):
"""
The response of the orchestrator agent.
- It can be a summary of the latest articles, an answer to a question, or an article response.
+ It can be a summary, question answer, article response, or general academic response.
"""
type: str = Field(
- description="The type of the response. Can be 'summary', 'question_answer', or 'article'."
+ description="The type of the response. Can be 'summary', 'question_answer', 'article', or 'general'."
)
- response: SummaryResponse | QuestionAnswerResponse | ArticleResponse = Field(
- description="The response to the request. It can be a summary of the latest articles,"
- + "an answer to a question, or an article response."
+ response: (
+ SummaryResponse | QuestionAnswerResponse | ArticleResponse | GeneralResponse
+ ) = Field(
+ description="The response to the request. It can be a summary of the latest articles, "
+ + "an answer to a question, an article response, or a general academic response."
)
@@ -101,6 +104,30 @@ async def answer_article(ctx: RunContext[Context], question: str) -> list[str]:
return r
+@orchestrator_agent_base.tool
+async def general_academic(ctx: RunContext[Context], request: str) -> list[str]:
+ """
+ Handle flexible academic requests that don't fit the standard categories.
+ This includes interdisciplinary questions, methodological guidance, concept explanations,
+ and novel request types. Use this tool when other tools don't clearly match the request.
+ Args:
+ ctx: the context
+ request: the academic request or question
+ Returns:
+ r: the response from the general academic agent
+ """
+ logger.info(
+ f"{datetime.now()}: Calling General Academic Agent with request: {request[:100]}..."
+ )
+
+ general_agent = GeneralAgent(
+ orchestrator_agent_base.model,
+ orchestrator_agent_base.model_settings,
+ )
+ r = await general_agent(request=request)
+ return r
+
+
if __name__ == "__main__":
import asyncio
diff --git a/src/askademic/prompts/general.py b/src/askademic/prompts/general.py
index 2f75c02..688de97 100644
--- a/src/askademic/prompts/general.py
+++ b/src/askademic/prompts/general.py
@@ -53,49 +53,58 @@
SYSTEM_PROMPT_ORCHESTRATOR = cleandoc(
"""
- You are an orchestrator agent, you delegate the request to the best tool
- based on its content. You have 3 tools to choose from:
- 1. summarise_latest_articles: to summarise papers
- 2. answer_question: to search for a list of articles based on a question
- 3. answer_article: to retrieve a specific article and answer a question about it
-
- Strictly follow these general instructions:
-
- 1. Delegate the request to the most appropriate agent
- 2. Delegate the request only once
- 3. Do not delegate the request to multiple agents
- 4. Accept the first response you get, stopping there
-
-
- In order to decide the agent to delegate the request to, follow these delegation instructions:
-
- * When receiving a request about summarising the latest articles,
- use the "summarise_latest_articles" tool.
- Example of requests for this tool:
- - "Summarise the latest articles in the field of quantum computing."
- - "What are the latest advancements in machine learning?"
- - "Find me the most recent articles about reinforcement learning."
- - "Summarise the latest articles in quantitative finance."
- * When the request is about searching for articles based on a question,
- use the question as an argument for the "answer_question" tool and wait for its response.
- Example of requests for this tool:
- - "How good is random forest at extrapolating?"
- - "Is BERT more accurate than RoBERTa in classification tasks?"
- - "What is the best way to design an experiment in sociology?"
- * When the request is about a single specific article,
- use the "answer_article" tool and wait for its response.
- Example of requests for this tool:
- - "Tell me more about 1234.5678?"
- - "What is the article 'Attention is all you need' about?"
- - "Tell me more about this article http://arxiv.org/pdf/2108.12542v2.
- How is the Donor Pool defined?"
-
+ You are an intelligent orchestrator agent that routes academic requests to the most appropriate handler.
+ You have 4 tools to choose from:
+ 1. summarise_latest_articles: for recent paper summaries in specific categories
+ 2. answer_question: for research questions requiring paper search and analysis
+ 3. answer_article: for retrieving and analyzing specific papers
+ 4. general_academic: for flexible academic requests that don't fit the above categories
+
+ Core principles:
+
+ 1. Choose the BEST tool for the request, considering partial matches
+ 2. Prefer being helpful over strict categorization
+ 3. When uncertain, favor general_academic over rejection
+ 4. Route to specialized tools when there's a clear match
+ 5. Accept the first response and stop there
+
+
+ Tool selection guidelines:
+
+ * Use "summarise_latest_articles" for:
+ - Requests for recent/latest papers in specific fields
+ - Category-based paper summaries
+ - "What's new in [field]" type questions
+ Examples: "Latest ML papers", "Recent quantum computing research"
+
+ * Use "answer_question" for:
+ - Specific research questions requiring evidence from multiple papers
+ - Comparative analysis questions
+ - "How does X work?" or "What is the state of Y?" questions
+ Examples: "How effective is BERT vs RoBERTa?", "What are the challenges in NLP?"
+
+ * Use "answer_article" for:
+ - Requests about specific papers (by title, ID, or URL)
+ - Questions about particular articles
+ Examples: "Tell me about paper 1234.5678", "Analyze the Attention paper"
+
+ * Use "general_academic" for:
+ - Interdisciplinary requests
+ - Novel question types
+ - Academic guidance or explanations
+ - Edge cases that don't clearly fit above categories
+ - Methodological questions
+ - Requests mixing multiple categories
+ Examples: "How to design experiments?", "Explain concept X", "Academic writing help"
+
+
+ When in doubt, prefer general_academic - it's better to attempt a helpful response than to reject a request.
The output must be a JSON object with the following structure:
{{
"response": {{
- "type": "summary" | "question_answer" | "article",
+ "type": "summary" | "question_answer" | "article" | "general",
"data":
}}
}}
@@ -331,3 +340,25 @@
)
#######################################
+
+# ############## General Agent ##############
+
+SYSTEM_PROMPT_GENERAL = """
+You are a flexible academic research assistant that handles diverse scholarly requests.
+
+You have access to arXiv search tools and can:
+- Search for papers by abstract content or title
+- Retrieve and analyze specific papers
+- Provide academic guidance and explanations
+- Handle interdisciplinary questions
+- Adapt to novel request types
+
+When you receive a request:
+1. Analyze what type of academic help is needed
+2. Use available tools to gather relevant information
+3. Provide helpful responses even for edge cases
+4. Suggest follow-up actions when appropriate
+5. Be transparent about limitations
+
+Always aim to be helpful rather than rejecting requests that don't fit narrow categories.
+"""