Metadata-Version: 2.4
Name: langchain-inspire-hep
Version: 0.2.0
Summary: INSPIRE HEP tools and API wrapper for LangChain.
License: MIT
Project-URL: Homepage, https://inspirehep.net
Project-URL: API Documentation, https://github.com/inspirehep/rest-api-doc
Project-URL: LangChain, https://python.langchain.com/
Keywords: langchain,physics,research,inspirehep,hep
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: langchain-core>=0.2.0
Requires-Dist: pydantic<3,>=1.10
Requires-Dist: requests>=2.31.0
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"

# INSPIRE HEP Tools for LangChain

Integration with [INSPIRE HEP](https://inspirehep.net), the trusted community hub for high energy physics research literature and job postings.

## Overview

This package provides four LangChain tools backed by a single `INSPIREHEPAPIWrapper`:

| Tool | Wrapper method | Returns |
|---|---|---|
| `INSPIRESearchLiteratureTool` | `search_literature(query, sort)` | `list[LiteratureRecord]` |
| `INSPIREGetAuthorPapersTool` | `get_author_papers(author_name, sort)` | `list[LiteratureRecord]` |
| `INSPIREGetPaperDetailsTool` | `get_paper_details(record_id)` | `PaperDetails` |
| `INSPIRESearchJobsTool` | `search_jobs(query, sort, status)` | `list[JobPosting]` |

All four return **typed Pydantic models**, not formatted strings — you get real fields back (`.title`, `.record_id`, `.deadline`, ...), so you can filter, sort, feed into a prompt template, or drop straight into a vector store without re-parsing text.

## Installation
```bash
pip install langchain-inspire-hep
```

Import paths remain under `langchain_community` after installation.

## Quick Start
```python
from langchain_community.tools.inspire_hep import INSPIRESearchLiteratureTool

tool = INSPIRESearchLiteratureTool()

results = tool.invoke({"query": "quantum field theory"})
for paper in results:
    print(paper.record_id, paper.title, paper.citation_count)
```

## All Four Tools
```python
from langchain_community.tools.inspire_hep import (
    INSPIRESearchLiteratureTool,
    INSPIREGetAuthorPapersTool,
    INSPIREGetPaperDetailsTool,
    INSPIRESearchJobsTool,
)

# Search for papers on a topic -> list[LiteratureRecord]
search_tool = INSPIRESearchLiteratureTool()
papers = search_tool.invoke({"query": "quantum gravity", "sort": "mostrecent"})

# Get an author's papers (requires INSPIRE identifier) -> list[LiteratureRecord]
author_tool = INSPIREGetAuthorPapersTool()
papers = author_tool.invoke({"author_name": "Witten.Edward.1", "sort": "mostcited"})

# Get details of a specific paper -> PaperDetails
details_tool = INSPIREGetPaperDetailsTool()
paper = details_tool.invoke({"record_id": "451647"})  # Maldacena's AdS/CFT paper

# Search job postings -> list[JobPosting]
jobs_tool = INSPIRESearchJobsTool()
jobs = jobs_tool.invoke({"query": "postdoc cosmology", "status": "open"})
```

## Output Models

Defined in `langchain_community.utilities.inspire_hep` and re-exported from `langchain_community.tools.inspire_hep`.

### `LiteratureRecord`
Returned (as a list) by `search_literature` and `get_author_papers`.

| Field | Type | Notes |
|---|---|---|
| `record_id` | `str` | Pass to `INSPIREGetPaperDetailsTool` for the full record |
| `title` | `str` | |
| `citation_count` | `int` | Defaults to `0` |

### `PaperDetails`
Returned by `get_paper_details`.

| Field | Type | Notes |
|---|---|---|
| `record_id` | `str` | |
| `title` | `str` | |
| `authors` | `list[str]` | Up to the first 3 authors |
| `citation_count` | `int` | |
| `abstract` | `str \| None` | Full abstract text, not truncated |

### `JobPosting`
Returned (as a list) by `search_jobs`. This is the fullest record in the package — enough to answer most questions about a single posting without a follow-up call:

| Field | Type | Notes |
|---|---|---|
| `record_id` | `str` | |
| `position` | `str` | |
| `institutions` | `list[str]` | |
| `ranks` | `list[str]` | e.g. `["POSTDOC"]`, `["SENIOR"]` |
| `regions` | `list[str]` | |
| `deadline` | `str \| None` | ISO date, if listed |
| `status` | `str \| None` | `"open"` or `"closed"` |
| `description` | `str \| None` | **Full posting text**, HTML tags stripped |
| `urls` | `list[str]` | External links, typically the application page |
| `contact_details` | `list[ContactDetail]` | See below |

### `ContactDetail`
| Field | Type |
|---|---|
| `name` | `str \| None` |
| `email` | `str \| None` |

All models support `.model_dump()` / `.model_dump_json()` for plain dicts/JSON, e.g. for RAG ingestion or logging.

## Using with AI Agents
```python
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.tools.inspire_hep import (
    INSPIRESearchLiteratureTool,
    INSPIREGetAuthorPapersTool,
    INSPIRESearchJobsTool,
)

tools = [
    INSPIRESearchLiteratureTool(),
    INSPIREGetAuthorPapersTool(),
    INSPIRESearchJobsTool(),
]

llm = ChatOpenAI(model="gpt-4", temperature=0)

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a physics research assistant with access to INSPIRE HEP."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = agent_executor.invoke({
    "input": "What postdoc positions in cosmology are open, and how do I apply?"
})
print(result["output"])
```

Because `search_jobs` now returns `description`, `urls`, and `contact_details`, an agent can answer "what does this posting require," "how do I apply," and "who do I contact" directly from a single tool call — it no longer needs to tell the user to go look the posting up on the website.

## Direct API Access (Without Agents)

For direct API access without LangChain agents:
```python
from langchain_community.utilities.inspire_hep import INSPIREHEPAPIWrapper

wrapper = INSPIREHEPAPIWrapper(top_k_results=5)

papers = wrapper.search_literature("quantum gravity", sort="mostcited")
author_papers = wrapper.get_author_papers("Witten.Edward.1", sort="mostrecent")
details = wrapper.get_paper_details("451647")
jobs = wrapper.search_jobs("postdoc cosmology", status="open")

for job in jobs:
    print(f"{job.position} @ {', '.join(job.institutions)} — deadline {job.deadline}")
    print(job.description[:200], "...")
    print("Apply:", job.urls)
```

## Error Handling

The wrapper raises `ValueError` for API-level failures (404 / not found, 429 / rate limited, timeout, connection error) instead of embedding an error string in the result — this keeps the return type honestly typed (`list[JobPosting]` is always a list of real postings, never a mix of data and error text).

- **Calling the wrapper directly**: catch `ValueError`.
- **Calling a tool** (`.invoke(...)`): each tool sets `handle_tool_error=True`, so a `ValueError` from the wrapper is converted to a `ToolException` and returned as a short error string in the tool output instead of raising and breaking an agent's run loop.

```python
from langchain_community.utilities.inspire_hep import INSPIREHEPAPIWrapper

wrapper = INSPIREHEPAPIWrapper()
try:
    wrapper.get_paper_details("999999999")
except ValueError as e:
    print(e)  # "Record not found: literature/999999999"
```

## Sorting Options

- `search_literature`, `get_author_papers`: `mostrecent` (newest first) or `mostcited` (most cited first)
- `search_jobs`: `mostrecent` (newest postings first) or `deadline` (earliest application deadline first)

## Job Status Filter

`search_jobs(status=...)` accepts `"open"` (default, still accepting applications) or `"closed"` (past postings).

## Finding Author Identifiers

`get_author_papers` requires INSPIRE identifiers (format: `Lastname.Firstname.N`), not plain names:

1. Go to https://inspirehep.net/authors
2. Search for the author by name
3. Click on their profile
4. Use the identifier shown (e.g., `Witten.Edward.1`)

**Why?** Plain names are ambiguous (many physicists share the same name), while INSPIRE identifiers are unique.

## Advanced Search Syntax

INSPIRE HEP supports advanced search queries for `query` on both `search_literature` and `search_jobs`:
```python
wrapper.search_literature("topcite 1000+")          # highly cited papers
wrapper.search_literature("author:Witten")           # papers by an author
wrapper.search_literature("date 2020->2024")         # date range
wrapper.search_jobs("Ohio State")                     # institution name
wrapper.search_jobs("cosmology", sort="deadline")     # soonest deadline first
```

See the [INSPIRE HEP search guide](https://help.inspirehep.net/knowledge-base/inspire-paper-search/) for more syntax.

## API Rate Limiting

INSPIRE HEP enforces rate limits of **15 requests per 5 seconds per IP address**. Requests over the limit surface as `ValueError("Rate limit exceeded. Please wait 5 seconds.")` — avoid making rapid successive requests.

## Testing

```bash
# Unit tests (fast, no internet required, mocked API responses)
pytest tests/unit_tests/test_inspire_hep.py -v

# Integration tests (requires internet, real API calls)
pytest tests/integration_tests/test_inspire_hep_integrations.py -v

# All tests
pytest tests/ -v
```

## Known Limitations

1. **Author identifiers required**: `get_author_papers` works reliably only with INSPIRE identifiers, not plain names. Look up identifiers at https://inspirehep.net/authors.
2. **No historical/trend data**: `search_jobs` reflects INSPIRE's current live index only (open or recently closed postings). There's no persistence layer here — if you need to answer questions about hiring trends over time, you need to snapshot results yourself on a schedule.
3. **`description` is best-effort plain text**: HTML is stripped with a regex, not a full HTML parser, so unusual markup may leave stray whitespace.
4. **LLM compatibility**: agent performance depends on the LLM's tool-calling support for structured (list-of-object) tool outputs. Works well with OpenAI GPT-4, Anthropic Claude, and other models with strong tool-calling support.

## Example Use Cases

```python
# Research assistant
"What are the most influential papers on the AdS/CFT correspondence?"
→ search_literature(sort="mostcited")

# Literature review
"Find recent papers on quantum entanglement from the last year"
→ search_literature(sort="mostrecent")

# Author research
"What are Edward Witten's most cited contributions?"
→ get_author_papers(author_name="Witten.Edward.1", sort="mostcited")

# Paper deep dive
"Tell me about INSPIRE record 451647"
→ get_paper_details(record_id="451647")

# Job search
"What postdoc positions in cosmology are open, and what's the deadline?"
→ search_jobs(query="cosmology", sort="deadline")

# Job posting detail (needs the enriched fields)
"What does this posting require, and who do I email?"
→ search_jobs(...) then read .description and .contact_details
```

## Citation

If you use INSPIRE HEP in your research, please cite:
```bibtex
@article{Moskovic:2021zjs,
    author = "Moskovic, Micha",
    title = "{The INSPIRE REST API}",
    url = "https://github.com/inspirehep/rest-api-doc",
    doi = "10.5281/zenodo.5788550",
    month = "12",
    year = "2021"
}
```

## Contributing

Contributions and issue reports are welcome. Possible future enhancements:

- Conference search
- Citation graph traversal
- Batch operations
- A persistence/snapshot layer for trend analysis over job postings

## Resources

- [INSPIRE HEP Website](https://inspirehep.net)
- [INSPIRE API Documentation](https://github.com/inspirehep/rest-api-doc)
- [LangChain Documentation](https://python.langchain.com/)
- [LangChain Contributing Guide](https://github.com/langchain-ai/langchain/blob/master/CONTRIBUTING.md)

## License

Released under the MIT License.
