|
12 | 12 | logger = structlog.get_logger(__name__) |
13 | 13 |
|
14 | 14 |
|
15 | | -@app.task(bind=True, max_retries=3, autoretry_for=(OpenAIError,)) |
16 | | -def summarize_content( |
17 | | - self, content: str, title: str, model: str, max_length: int = 256 |
18 | | -) -> str: |
19 | | - """Summarize content using an LLM model.""" |
| 15 | +def generate_summary( |
| 16 | + content: str, |
| 17 | + title: str, |
| 18 | + model: str, |
| 19 | + max_length: int = 256, |
| 20 | + timeout: float = 30, |
| 21 | +) -> str | None: |
| 22 | + """Build the summarization prompt and call OpenRouter synchronously. |
| 23 | +
|
| 24 | + Plain function (no Celery decoration) so callers can use it inline with an |
| 25 | + explicit ``timeout``. |
| 26 | + Background callers go through ``summarize_content`` below, which wraps this |
| 27 | + in a Celery task so ``autoretry_for=(OpenAIError,)`` and ``max_retries`` fire. |
| 28 | +
|
| 29 | + Raises ValueError on empty content, OpenAIError on API failures. |
| 30 | + Returns the summary string, or None if the response is malformed. |
| 31 | + """ |
20 | 32 | if not content: |
21 | 33 | logger.warning("No content provided to summarize, skipping.") |
22 | 34 | raise ValueError("No content provided to summarize.") |
@@ -55,17 +67,33 @@ def summarize_content( |
55 | 67 | {"role": "user", "content": user_prompt}, |
56 | 68 | ] |
57 | 69 | logger.debug(f"{messages=}") |
58 | | - content = None |
| 70 | + client = OpenAI( |
| 71 | + base_url=OPENROUTER_URL, api_key=OPENROUTER_API_KEY, timeout=timeout |
| 72 | + ) |
| 73 | + response = client.chat.completions.create(model=model, messages=messages) |
59 | 74 | try: |
60 | | - client = OpenAI(base_url=OPENROUTER_URL, api_key=OPENROUTER_API_KEY) |
61 | | - response = client.chat.completions.create(model=model, messages=messages) |
62 | | - content = response.choices[0].message.content |
63 | | - logger.info( |
64 | | - f"Received summarized content for {content[:100]=}: {len(content)=}..." |
65 | | - ) |
| 75 | + summary = response.choices[0].message.content |
66 | 76 | except (AttributeError, IndexError) as e: |
67 | 77 | logger.error(f"Error getting summarized content: {e=}") |
68 | | - return content |
| 78 | + return None |
| 79 | + logger.info( |
| 80 | + f"Received summarized content for {summary[:100]=}: {len(summary)=}..." |
| 81 | + ) |
| 82 | + return summary |
| 83 | + |
| 84 | + |
| 85 | +@app.task(bind=True, max_retries=3, autoretry_for=(OpenAIError,)) |
| 86 | +def summarize_content( |
| 87 | + self, content: str, title: str, model: str, max_length: int = 256 |
| 88 | +) -> str | None: |
| 89 | + """Celery wrapper around ``generate_summary``. |
| 90 | +
|
| 91 | + Runs in a worker so ``autoretry_for=(OpenAIError,)`` and ``max_retries`` |
| 92 | + apply to transient OpenRouter blips. For synchronous, inline callers, use |
| 93 | + ``generate_summary`` directly (so retries don't silently no-op and you can |
| 94 | + set a tight timeout). |
| 95 | + """ |
| 96 | + return generate_summary(content, title, model, max_length) |
69 | 97 |
|
70 | 98 |
|
71 | 99 | @app.task |
|
0 commit comments