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. +"""