By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
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.
A library catalog entry needs to represent books, journals, digital resources, and their availability.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for financial services in your browser — 60 seconds, no signup.
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
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 = ""
One of the most common library requests is help formatting citations. The agent needs a reliable formatter.
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)
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],
)
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.
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.
Still reading? Stop comparing — try CallSphere live.
See the financial services AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
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

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A how-to guide for Bangladeshi coaching centres, edtech startups, and schools on using a CallSphere AI voice + chat agent to answer every admission enquiry in Bengali and English during peak season.
A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.
A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco