Building a Library Research Agent: Book Search, Citation Help, and Resource Recommendations
Create an AI-powered library research agent that searches catalogs, formats citations in multiple styles, handles inter-library loan requests, and recommends related academic resources.
The Modern Library Challenge
Academic libraries hold vast collections across physical stacks, digital databases, and inter-library loan networks. Students often struggle to find the right resources, format citations correctly, or even know which databases to search. A library research agent transforms this experience by providing intelligent catalog search, automatic citation generation, and personalized resource recommendations.
Modeling the Library Catalog
A library catalog entry needs to represent books, journals, digital resources, and their availability.
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
from datetime import date
class ResourceType(Enum):
BOOK = "book"
JOURNAL = "journal"
ARTICLE = "article"
EBOOK = "ebook"
THESIS = "thesis"
CONFERENCE_PAPER = "conference_paper"
class AvailabilityStatus(Enum):
AVAILABLE = "available"
CHECKED_OUT = "checked_out"
ON_HOLD = "on_hold"
DIGITAL = "digital"
ILL_AVAILABLE = "inter_library_loan"
class CitationStyle(Enum):
APA = "apa"
MLA = "mla"
CHICAGO = "chicago"
IEEE = "ieee"
@dataclass
class LibraryResource:
resource_id: str
title: str
authors: list[str]
resource_type: ResourceType
year: int
isbn: Optional[str] = None
doi: Optional[str] = None
publisher: str = ""
journal_name: str = ""
volume: str = ""
issue: str = ""
pages: str = ""
subjects: list[str] = field(default_factory=list)
availability: AvailabilityStatus = AvailabilityStatus.AVAILABLE
location: str = ""
call_number: str = ""
abstract: str = ""
Citation Formatter
One of the most common library requests is help formatting citations. The agent needs a reliable formatter.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
def format_citation(
resource: LibraryResource, style: CitationStyle
) -> str:
authors_str = _format_authors(resource.authors, style)
if style == CitationStyle.APA:
if resource.resource_type == ResourceType.BOOK:
return (
f"{authors_str} ({resource.year}). "
f"*{resource.title}*. {resource.publisher}."
)
elif resource.resource_type in (
ResourceType.ARTICLE, ResourceType.JOURNAL
):
return (
f"{authors_str} ({resource.year}). "
f"{resource.title}. *{resource.journal_name}*, "
f"*{resource.volume}*({resource.issue}), "
f"{resource.pages}."
)
elif style == CitationStyle.MLA:
if resource.resource_type == ResourceType.BOOK:
return (
f"{authors_str}. *{resource.title}*. "
f"{resource.publisher}, {resource.year}."
)
elif style == CitationStyle.IEEE:
if resource.resource_type == ResourceType.ARTICLE:
return (
f"{authors_str}, \"{resource.title},\" "
f"*{resource.journal_name}*, vol. {resource.volume}, "
f"no. {resource.issue}, pp. {resource.pages}, "
f"{resource.year}."
)
return f"{authors_str}. {resource.title}. {resource.year}."
def _format_authors(
authors: list[str], style: CitationStyle
) -> str:
if not authors:
return "Unknown"
if style == CitationStyle.APA:
if len(authors) == 1:
parts = authors[0].split()
return f"{parts[-1]}, {parts[0][0]}."
formatted = []
for author in authors[:6]:
parts = author.split()
formatted.append(f"{parts[-1]}, {parts[0][0]}.")
if len(authors) > 6:
return ", ".join(formatted) + ", ... et al."
return ", ".join(formatted[:-1]) + ", & " + formatted[-1]
return " and ".join(authors)
Agent Tools for Library Search and Recommendations
from agents import Agent, function_tool, Runner
import json
CATALOG: dict[str, LibraryResource] = {}
@function_tool
def search_catalog(
query: str,
resource_type: str = "",
subject: str = "",
) -> str:
"""Search the library catalog by keyword, type, and subject."""
results = []
query_lower = query.lower()
for res in CATALOG.values():
title_match = query_lower in res.title.lower()
author_match = any(
query_lower in a.lower() for a in res.authors
)
subject_match = any(
query_lower in s.lower() for s in res.subjects
)
if not (title_match or author_match or subject_match):
continue
if resource_type and res.resource_type.value != resource_type:
continue
if subject and not any(
subject.lower() in s.lower() for s in res.subjects
):
continue
results.append({
"id": res.resource_id,
"title": res.title,
"authors": res.authors,
"year": res.year,
"type": res.resource_type.value,
"availability": res.availability.value,
"location": res.location,
"call_number": res.call_number,
})
return json.dumps(results[:10]) if results else "No results found."
@function_tool
def generate_citation(
resource_id: str, style: str = "apa"
) -> str:
"""Generate a formatted citation for a resource."""
resource = CATALOG.get(resource_id)
if not resource:
return "Resource not found."
try:
citation_style = CitationStyle(style.lower())
except ValueError:
return f"Unsupported style. Use: apa, mla, chicago, ieee"
return format_citation(resource, citation_style)
@function_tool
def find_related_resources(resource_id: str) -> str:
"""Find resources related to a given resource by shared subjects."""
source = CATALOG.get(resource_id)
if not source:
return "Resource not found."
source_subjects = set(s.lower() for s in source.subjects)
related = []
for rid, res in CATALOG.items():
if rid == resource_id:
continue
res_subjects = set(s.lower() for s in res.subjects)
overlap = source_subjects & res_subjects
if overlap:
related.append({
"id": rid,
"title": res.title,
"authors": res.authors,
"shared_subjects": list(overlap),
"relevance_score": len(overlap) / len(source_subjects),
})
related.sort(key=lambda r: r["relevance_score"], reverse=True)
return json.dumps(related[:5])
library_agent = Agent(
name="Library Research Assistant",
instructions="""You are an academic library research assistant.
Help patrons search the catalog, generate properly formatted
citations, find related resources, and request inter-library
loans. When a resource is checked out, suggest alternatives
or offer to place a hold. Always ask which citation style the
patron needs before generating citations.""",
tools=[search_catalog, generate_citation, find_related_resources],
)
FAQ
How does the agent handle resources from external databases like JSTOR or PubMed?
Implement additional tool functions that call external APIs. JSTOR and PubMed provide REST APIs that return structured metadata. The agent can search these alongside the local catalog and clearly indicate which resources are available locally versus externally.
Can the agent detect plagiarism or verify citation accuracy?
The agent can verify that a citation matches the source metadata (correct authors, year, title) by comparing against catalog records. For plagiarism detection, integrate with services like Turnitin via their API. The agent should frame this as a verification service, not an accusation tool.
How do you handle multi-branch library systems?
Add a branch field to each resource and a preferred_branch to the patron record. The search tool returns availability per branch, and the agent can suggest the nearest location with an available copy or offer to initiate a transfer between branches.
#AIAgents #EdTech #LibraryScience #Python #Research #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.