From 3c54415522b9edd89dae8b0a4ba32748f817be4d Mon Sep 17 00:00:00 2001 From: Aditya Advani Date: Tue, 23 Dec 2025 16:36:01 -0500 Subject: [PATCH 1/6] Add new model aliases and fix literature arXiv guard --- denario/langgraph_agents/literature.py | 4 ++- denario/llm.py | 38 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/denario/langgraph_agents/literature.py b/denario/langgraph_agents/literature.py index 07cff977..a8b2c350 100644 --- a/denario/langgraph_agents/literature.py +++ b/denario/langgraph_agents/literature.py @@ -101,6 +101,8 @@ def semantic_scholar(state: GraphState, config: RunnableConfig): paper_str = f"""{papers_analyzed+state['literature']['num_papers']}. {title} ({year})\nAuthors: {authors}\nAbstract: {abstract}\nURL: {url}""" # extract arXiv link, if any + arXiv_pdf = None + arXiv_pdf2 = None if externalID: arXiv = externalID.get("ArXiv", None) if arXiv: @@ -111,7 +113,7 @@ def semantic_scholar(state: GraphState, config: RunnableConfig): # extract pdf link, if any if pdf: pdf = pdf.get('url', None) - if pdf and pdf!=arXiv_pdf and pdf!=arXiv_pdf2: + if pdf and (arXiv_pdf is None or (pdf!=arXiv_pdf and pdf!=arXiv_pdf2)): paper_str = f"{paper_str}\npdf: {pdf}" # put these papers in the literature.log diff --git a/denario/llm.py b/denario/llm.py index 012f2553..994d1f67 100644 --- a/denario/llm.py +++ b/denario/llm.py @@ -25,6 +25,16 @@ class LLM(BaseModel): temperature=0.7) """`gemini-2.5-pro` model.""" +gemini3propreview = LLM(name="gemini-3-pro-preview", + max_output_tokens=65536, + temperature=0.7) +"""`gemini-3-pro-preview` model.""" + +gemini3flashpreview = LLM(name="gemini-3-flash-preview", + max_output_tokens=65536, + temperature=0.7) +"""`gemini-3-flash-preview` model.""" + o3mini = LLM(name="o3-mini-2025-01-31", max_output_tokens=100000, temperature=None) @@ -60,6 +70,16 @@ class LLM(BaseModel): temperature=None) """`gpt-5` model """ +gpt52 = LLM(name="gpt-5.2", + max_output_tokens=128000, + temperature=None) +"""`gpt-5.2` model.""" + +gpt52pro = LLM(name="gpt-5.2-pro", + max_output_tokens=128000, + temperature=None) +"""`gpt-5.2-pro` model.""" + gpt5mini = LLM(name="gpt-5-mini", max_output_tokens=128000, temperature=None) @@ -80,10 +100,24 @@ class LLM(BaseModel): temperature=0) """`claude-4.1-Opus` model.""" +claude45sonnet = LLM(name="claude-sonnet-4-5-20250929", + max_output_tokens=64000, + temperature=0) +"""`claude-4.5-Sonnet` model.""" + +claude45opus = LLM(name="claude-opus-4-5-20251101", + max_output_tokens=64000, + temperature=0) +"""`claude-4.5-Opus` model.""" + models : Dict[str, LLM] = { "gemini-2.0-flash" : gemini20flash, "gemini-2.5-flash" : gemini25flash, "gemini-2.5-pro" : gemini25pro, + "gemini-3-pro" : gemini3propreview, + "gemini-3-pro-preview" : gemini3propreview, + "gemini-3-flash" : gemini3flashpreview, + "gemini-3-flash-preview" : gemini3flashpreview, "o3-mini" : o3mini, "gpt-4o" : gpt4o, "gpt-4.1" : gpt41, @@ -91,9 +125,13 @@ class LLM(BaseModel): "gpt-4o-mini" : gpt4omini, "gpt-4.5" : gpt45, "gpt-5" : gpt5, + "gpt-5.2" : gpt52, + "gpt-5.2-pro" : gpt52pro, "gpt-5-mini" : gpt5mini, "claude-3.7-sonnet" : claude37sonnet, "claude-4-opus" : claude4opus, "claude-4.1-opus" : claude41opus, + "claude-4.5-sonnet" : claude45sonnet, + "claude-4.5-opus" : claude45opus, } """Dictionary with the available models.""" From d29d9ed73f93f372ff978abe8e114f8631d36df2 Mon Sep 17 00:00:00 2001 From: Aditya Advani Date: Tue, 23 Dec 2025 16:37:54 -0500 Subject: [PATCH 2/6] Add canonical Claude 4.5 model IDs --- denario/llm.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/denario/llm.py b/denario/llm.py index 994d1f67..76e0c1c8 100644 --- a/denario/llm.py +++ b/denario/llm.py @@ -100,16 +100,26 @@ class LLM(BaseModel): temperature=0) """`claude-4.1-Opus` model.""" -claude45sonnet = LLM(name="claude-sonnet-4-5-20250929", +claude45sonnet = LLM(name="claude-sonnet-4-5", max_output_tokens=64000, temperature=0) """`claude-4.5-Sonnet` model.""" -claude45opus = LLM(name="claude-opus-4-5-20251101", +claude45sonnet_20250929 = LLM(name="claude-sonnet-4-5-20250929", + max_output_tokens=64000, + temperature=0) +"""`claude-4.5-Sonnet` snapshot model.""" + +claude45opus = LLM(name="claude-opus-4-5", max_output_tokens=64000, temperature=0) """`claude-4.5-Opus` model.""" +claude45opus_20251101 = LLM(name="claude-opus-4-5-20251101", + max_output_tokens=64000, + temperature=0) +"""`claude-4.5-Opus` snapshot model.""" + models : Dict[str, LLM] = { "gemini-2.0-flash" : gemini20flash, "gemini-2.5-flash" : gemini25flash, @@ -132,6 +142,8 @@ class LLM(BaseModel): "claude-4-opus" : claude4opus, "claude-4.1-opus" : claude41opus, "claude-4.5-sonnet" : claude45sonnet, + "claude-4.5-sonnet-20250929" : claude45sonnet_20250929, "claude-4.5-opus" : claude45opus, + "claude-4.5-opus-20251101" : claude45opus_20251101, } """Dictionary with the available models.""" From d314df5a07e74be91e547459aa559ce1a884f695 Mon Sep 17 00:00:00 2001 From: Aditya Advani Date: Tue, 23 Dec 2025 17:07:55 -0500 Subject: [PATCH 3/6] Add Claude 4.5 Haiku model IDs --- denario/llm.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/denario/llm.py b/denario/llm.py index 76e0c1c8..e2ad7fc6 100644 --- a/denario/llm.py +++ b/denario/llm.py @@ -110,6 +110,16 @@ class LLM(BaseModel): temperature=0) """`claude-4.5-Sonnet` snapshot model.""" +claude45haiku = LLM(name="claude-haiku-4-5", + max_output_tokens=64000, + temperature=0) +"""`claude-4.5-Haiku` model.""" + +claude45haiku_20251001 = LLM(name="claude-haiku-4-5-20251001", + max_output_tokens=64000, + temperature=0) +"""`claude-4.5-Haiku` snapshot model.""" + claude45opus = LLM(name="claude-opus-4-5", max_output_tokens=64000, temperature=0) @@ -143,6 +153,8 @@ class LLM(BaseModel): "claude-4.1-opus" : claude41opus, "claude-4.5-sonnet" : claude45sonnet, "claude-4.5-sonnet-20250929" : claude45sonnet_20250929, + "claude-4.5-haiku" : claude45haiku, + "claude-4.5-haiku-20251001" : claude45haiku_20251001, "claude-4.5-opus" : claude45opus, "claude-4.5-opus-20251101" : claude45opus_20251101, } From a0dfd85870daa8049d7773c737c5e7664da2aff6 Mon Sep 17 00:00:00 2001 From: Aditya Advani Date: Tue, 23 Dec 2025 17:08:21 -0500 Subject: [PATCH 4/6] Remove snapshot model aliases --- denario/llm.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/denario/llm.py b/denario/llm.py index e2ad7fc6..866c49bc 100644 --- a/denario/llm.py +++ b/denario/llm.py @@ -105,30 +105,16 @@ class LLM(BaseModel): temperature=0) """`claude-4.5-Sonnet` model.""" -claude45sonnet_20250929 = LLM(name="claude-sonnet-4-5-20250929", - max_output_tokens=64000, - temperature=0) -"""`claude-4.5-Sonnet` snapshot model.""" - claude45haiku = LLM(name="claude-haiku-4-5", max_output_tokens=64000, temperature=0) """`claude-4.5-Haiku` model.""" -claude45haiku_20251001 = LLM(name="claude-haiku-4-5-20251001", - max_output_tokens=64000, - temperature=0) -"""`claude-4.5-Haiku` snapshot model.""" - claude45opus = LLM(name="claude-opus-4-5", max_output_tokens=64000, temperature=0) """`claude-4.5-Opus` model.""" -claude45opus_20251101 = LLM(name="claude-opus-4-5-20251101", - max_output_tokens=64000, - temperature=0) -"""`claude-4.5-Opus` snapshot model.""" models : Dict[str, LLM] = { "gemini-2.0-flash" : gemini20flash, @@ -152,10 +138,7 @@ class LLM(BaseModel): "claude-4-opus" : claude4opus, "claude-4.1-opus" : claude41opus, "claude-4.5-sonnet" : claude45sonnet, - "claude-4.5-sonnet-20250929" : claude45sonnet_20250929, "claude-4.5-haiku" : claude45haiku, - "claude-4.5-haiku-20251001" : claude45haiku_20251001, "claude-4.5-opus" : claude45opus, - "claude-4.5-opus-20251101" : claude45opus_20251101, } """Dictionary with the available models.""" From d9e642ba79c416703021607fc0097f6cfd45a40e Mon Sep 17 00:00:00 2001 From: Aditya Advani Date: Tue, 23 Dec 2025 17:11:16 -0500 Subject: [PATCH 5/6] Add smoke test for model aliases --- tests/smoke_models.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/smoke_models.py diff --git a/tests/smoke_models.py b/tests/smoke_models.py new file mode 100644 index 00000000..6c547a47 --- /dev/null +++ b/tests/smoke_models.py @@ -0,0 +1,25 @@ +from denario.llm import models + + +def main() -> None: + required = [ + "gemini-3-flash", + "gemini-3-pro", + "gpt-5.2", + "gpt-5.2-pro", + "claude-4.5-sonnet", + "claude-4.5-opus", + "claude-4.5-haiku", + ] + + missing = [name for name in required if name not in models] + if missing: + raise SystemExit(f"Missing model aliases: {missing}") + + print("Model alias smoke test passed:") + for name in required: + print(f"- {name} -> {models[name].name}") + + +if __name__ == "__main__": + main() From d1f3336aa8f318b17723205dbfaaa138850f3aba Mon Sep 17 00:00:00 2001 From: Aditya Advani Date: Fri, 2 Jan 2026 15:32:05 -0500 Subject: [PATCH 6/6] Skip citations when Perplexity key missing and harden API errors --- denario/paper_agents/literature.py | 29 ++++++++++++++++++++++++++++- denario/paper_agents/paper_node.py | 21 +++++++++++++++++---- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/denario/paper_agents/literature.py b/denario/paper_agents/literature.py index 36bc625e..d9802ce3 100644 --- a/denario/paper_agents/literature.py +++ b/denario/paper_agents/literature.py @@ -1,6 +1,7 @@ import re import requests from typing import List, Tuple +from requests.exceptions import JSONDecodeError as RequestsJSONDecodeError from ..key_manager import KeyManager @@ -15,8 +16,34 @@ def _execute_query(payload, keys: KeyManager): PerplexityChatCompletionResponse: Parsed response from the Perplexity API. """ api_key = keys.PERPLEXITY + if not api_key: + raise RuntimeError( + "PERPLEXITY_API_KEY is not set. Set it to enable add_citations=True, " + "or run get_paper(add_citations=False)." + ) headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} - response = requests.post("https://api.perplexity.ai/chat/completions", headers=headers, json=payload).json() + raw_response = requests.post( + "https://api.perplexity.ai/chat/completions", + headers=headers, + json=payload, + timeout=60, + ) + + if raw_response.status_code != 200: + body_preview = (raw_response.text or "").strip()[:500] + raise RuntimeError( + f"Perplexity API error {raw_response.status_code}. " + f"Body (first 500 chars): {body_preview}" + ) + + try: + response = raw_response.json() + except (ValueError, RequestsJSONDecodeError) as e: + body_preview = (raw_response.text or "").strip()[:500] + raise RuntimeError( + "Perplexity API returned non-JSON response. " + f"Body (first 500 chars): {body_preview}" + ) from e return response diff --git a/denario/paper_agents/paper_node.py b/denario/paper_agents/paper_node.py index 86216bd6..8d2cbee8 100644 --- a/denario/paper_agents/paper_node.py +++ b/denario/paper_agents/paper_node.py @@ -477,7 +477,11 @@ async def add_citations_async(state, text, section_name): loop = asyncio.get_event_loop() func = partial(process_tex_file_with_references, text, state["keys"]) - new_text, references = await loop.run_in_executor(None, func) + try: + new_text, references = await loop.run_in_executor(None, func) + except Exception as e: + print(f" {section_name} citations failed: {e}") + return section_name, text, "" new_text = clean_section(new_text, section_name) # save temporary file @@ -494,16 +498,26 @@ async def citations_node(state: GraphState, config: RunnableConfig): print("Adding citations...") + if not state["keys"].PERPLEXITY: + print("⚠️ PERPLEXITY_API_KEY not set; skipping citation insertion.") + return {'paper': state['paper'], + 'tokens': state['tokens']} + #sections = ['Introduction', 'Methods', 'Results', 'Conclusions'] sections = ['Introduction', 'Methods'] tasks = [add_citations_async(state, state['paper'][section], section) for section in sections] - results = await asyncio.gather(*tasks) + results = await asyncio.gather(*tasks, return_exceptions=True) # Deduplicate full BibTeX entries bib_entries_set = set() bib_entries_list = [] - for section_name, updated_text, references in results: + for result in results: + if isinstance(result, Exception): + print(f"⚠️ Citation task failed: {result}") + continue + + section_name, updated_text, references = result state['paper'][section_name] = updated_text @@ -553,4 +567,3 @@ async def citations_node(state: GraphState, config: RunnableConfig): return {'paper': state['paper'], 'tokens': state['tokens']} ####################################################################################### -