diff --git a/asknews_sdk/dto/alert.py b/asknews_sdk/dto/alert.py index 06a3c0b..b1a0a07 100644 --- a/asknews_sdk/dto/alert.py +++ b/asknews_sdk/dto/alert.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, List, Literal, Optional, Union +from typing import Any, Dict, List, Literal, Optional, Union, get_args from uuid import UUID from pydantic import BaseModel, EmailStr, Field, RootModel @@ -23,7 +23,7 @@ "gpt-4.1-mini-2025-04-14", ] -ReportModel = Literal[ +AlertReportModel = Literal[ # "gpt-5", "gpt-4o", "gpt-4.1-2025-04-14", @@ -33,7 +33,7 @@ "claude-sonnet-4-20250514", "claude-sonnet-4-5-20250929", "claude-opus-4-5-20251101", - "claude-opuse-4-6", + "claude-opus-4-6", "meta-llama/Meta-Llama-3.1-405B-Instruct", "meta-llama/Meta-Llama-3.3-70B-Instruct", ] @@ -91,8 +91,75 @@ class BlueskySource(BaseModel): params: Optional[BlueskySourceParams] = Field(None, description="Bluesky source parameters") +DeepNewsSourceType = Literal["asknews", "google", "graph", "wiki", "x", "reddit", "charts"] +DeepNewsSourceTypeDefault: DeepNewsSourceType = "asknews" + +DeepNewsInlineCitationType = Literal["markdown_link", "numbered", "none"] +DeepNewsInlineCitationTypeDefault: DeepNewsInlineCitationType = "markdown_link" + + +class DeepNewsSourceParams(BaseModel): + model: Optional[str] = Field( + default=None, + description=("The model to use for DeepNews. Check API reference for default model."), + ) + filter_params: Optional[Dict[str, Any]] = Field( + default=None, description="Any filter param available on the /news endpoint." + ) + search_depth: Optional[int] = Field( + default=2, + description=( + "The search depth for deep research. Higher values mean more " "thorough research." + ), + ) + max_depth: Optional[int] = Field(default=4, description="The maximum research depth allowed.") + sources: Optional[Union[DeepNewsSourceType, List[DeepNewsSourceType]]] = Field( + default=DeepNewsSourceTypeDefault, + description=( + "Which data sources DeepNews should use. Can be a single source or a list. " + f"Available sources are: {', '.join(get_args(DeepNewsSourceType))}" + ), + ) + journalist_mode: Optional[bool] = Field( + default=True, + description=( + "Activate journalist mode, with improved alignment for making claims " + "with supporting evidence. Improved journalistic style." + ), + ) + include_entities: Optional[bool] = Field( + default=True, + description=( + "Include entities of the sources in the internal context. " + "Activating this will increase internal token usage." + ), + ) + include_graphs: Optional[bool] = Field( + default=False, + description=( + "Include graphs of the sources in the internal context. " + "Activating this will increase internal token usage." + ), + ) + include_coordinates: Optional[bool] = Field( + default=False, + description=( + "Include geocoordinates of the sources in the internal context. " + "Activating this will increase internal token usage." + ), + ) + asknews_watermark: Optional[bool] = Field( + default=True, description='Append "Generated by AskNews AI" watermark.' + ) + + +class DeepNewsSource(BaseModel): + identifier: Literal["deepnews"] + params: Optional[DeepNewsSourceParams] = Field(None, description="DeepNews source parameters") + + Source = Annotated[ - Union[AskNewsSource, TelegramSource, BlueskySource, WebSource], + Union[AskNewsSource, TelegramSource, BlueskySource, WebSource, DeepNewsSource], Field(discriminator="identifier"), ] @@ -177,65 +244,101 @@ class ReportRequest(BaseModel): ["human", "{summaries}"], ], ) - model: Optional[ReportModel] = Field( - None, description="The model to use for the report", examples=["gpt-4o"] + model: Optional[AlertReportModel] = Field( + None, + description=( + "The model to use for the report. Check the API reference for " + "default model. If you have DeepNews as a source, the DeepNews " + "model will be used for the report." + ), + examples=["gpt-4o"], ) logo_url: Optional[HttpUrlString] = Field( None, description="The logo URL to use for the report" ) +AlertType = Literal["AlertRepeat", "AlertOnce", "ReportRepeat", "ReportOnce"] + + class CreateAlertRequest(BaseSchema): - query: Optional[str] = Field( - None, - description=( - "The query to run for the alert. For example you ask for " - '"I want to be alerted if there is a protest in paris"' - ), + query: str = Field( + ..., + description=("The query to run for the alert."), examples=[ "I want to be alerted if the president of the US says something about the economy", ], ) - cron: CronStr = Field( + sources: Sources = Field( ..., description=( - "The cron schedule for the alert. For example hourly is '0 * * * *'." - " See https://crontab.run/ for more examples" + "The sources to use for the alert query. Available sources are: " + f"{', '.join([arg.__name__ for arg in get_args(get_args(Source)[0])])}" + ), + ) + alert_type: Optional[AlertType] = Field( + None, + description=( + "The type of alert. If specified, overrides 'repeat'. " + "'AlertRepeat': trigger alert actions any time the alert query is satisfied. " + "'AlertOnce': trigger alert actions when the alert query is satisfied " + "and then disable the alert. " + "'ReportRepeat': trigger alert actions and write a report any time the alert " + "query is satisfied. If not specified, the default report model (see API " + "reference) is used. " + "'ReportOnce': trigger alert actions and write a report when the alert " + "query is satisfied and then disable the alert. If not specified, " + "the default report model (see API reference) is used." ), - examples=[ - "*/15 * * * *", - ], ) model: Optional[CheckAlertModel] = Field( - ..., - description="The model to use for the alert check", + None, + description=( + "The model that is used to check if the alert conditions are satisfied by " + "sources (this is not the same as the model used to write the report.)" + "Check the API reference for default model." + ), examples=[ "meta-llama/Meta-Llama-3.1-8B-Instruct", ], ) - share_link: Optional[HttpUrlString] = Field( - None, description="The newsplunker share link to update when the alert triggers" + cron: CronStr = Field( + ..., + description=( + "How often or when to check sources for this alert, specified as a cron expression. " + "Examples: '*/15 * * * *' (every 15 minutes), '0 * * * *' (hourly), " + "'0 9 * * *' (daily at 9am), '0 9 * * 1' (Mondays at 9am). " + " See https://crontab.run/ for more examples." + ), + examples=[ + "'0 0 * * *' (daily at midnight UTC)", + ], ) - sources: Sources = Field(..., description="The sources to use for the alert query") - report: Optional[ReportRequest] = Field( - None, description="The report to generate when the alert triggers" + triggers: Triggers = Field( + ..., + description=( + "Configuration for actions to trigger for the alert. Available actions are: " + f"{', '.join([arg.__name__ for arg in get_args(get_args(Trigger)[0])])}" + ), ) - triggers: Triggers = Field(..., description="The triggers to use for the alert") - always_trigger: bool = Field( + always_trigger: Optional[bool] = Field( False, description=( - "Whether to always trigger the alert. Default is False. This skips the " - "alert check and run triggers immediately" + "Whether to always trigger the actions when sources are scanned. This skips the " + "check for if the alert conditions are satisfied and run triggers immediately. " + "Defaults to False." ), ) - repeat: bool = Field( + repeat: Optional[bool] = Field( True, description=( "Whether to repeat the alert. Default is True. If False, the alert will be " "disabled after it triggers once" ), ) - active: bool = Field(True, description="Whether the alert is active or not. Default is True.") + active: Optional[bool] = Field( + True, description="Whether the alert is active or not. Default is True." + ) expires_at: Optional[datetime] = Field( None, description=( @@ -243,59 +346,89 @@ class CreateAlertRequest(BaseSchema): "If set, the alert will be disabled after this date." ), ) + report: Optional[ReportRequest] = Field( + None, + description=( + "Configuration for generating a written report when the alert triggers. " + "If not specified, no report is generated." + ), + ) + title: Optional[str] = Field( + None, + description="The title of the alert. If not provided, no title will be used.", + examples=["Alert for US President's statements on the economy"], + ) + share_link: Optional[HttpUrlString] = Field( + None, description="The Newsplunker share link to update when the alert triggers" + ) class UpdateAlertRequest(BaseSchema): query: Optional[str] = Field( None, - description=( - "The query to run for the alert. For example you ask for " - '"I want to be alerted if there is a protest in paris"' - ), + description=("The query to run for the alert."), examples=[ "I want to be alerted if the president of the US says something about the economy", ], ) - cron: Optional[CronStr] = Field( + sources: Optional[Sources] = Field( None, description=( - "The cron schedule for the alert. For example hourly is '0 * * * *'." - " See https://crontab.run/ for more examples" + "The sources to use for the alert query. Available sources are: " + f"{', '.join([arg.__name__ for arg in get_args(get_args(Source)[0])])}" ), - examples=[ - "*/15 * * * *", - ], ) - model: Optional[CheckAlertModel] = Field( + alert_type: Optional[AlertType] = Field( None, - description="The model to use for the alert check", - examples=[ - "meta-llama/Meta-Llama-3.1-8B-Instruct", - ], + description=( + "The type of alert. If specified, overrides 'repeat'. " + "'AlertRepeat': trigger alert actions any time the alert query is satisfied. " + "'AlertOnce': trigger alert actions when the alert query is satisfied " + "and then disable the alert. " + "'ReportRepeat': trigger alert actions and write a report any time the alert " + "query is satisfied. " + "'ReportOnce': trigger alert actions and write a report when the alert " + "query is satisfied and then disable the alert." + ), ) - share_link: Optional[HttpUrlString] = Field( - None, description="The newsplunker share link to update when the alert triggers" + model: Optional[CheckAlertModel] = Field( + None, + description=( + "The model that is used to check if the alert conditions are satisfied by " + "sources (this is not the same as the model used to write the report.)" + ), + examples=["gpt-4o-mini"], ) - filter_params: Optional[FilterParams] = Field( - None, description="The filter params to use for the alert query" + cron: Optional[CronStr] = Field( + None, + description=( + "How often or when to check sources for this alert, specified as a cron expression. " + "Examples: '*/15 * * * *' (every 15 minutes), '0 * * * *' (hourly), " + "'0 9 * * *' (daily at 9am), '0 9 * * 1' (Mondays at 9am). " + " See https://crontab.run/ for more examples." + ), + examples=["'0 0 * * *' (daily at midnight UTC)"], ) - sources: Optional[Sources] = Field(None, description="The sources to use for the alert query") - report: Optional[ReportRequest] = Field( - None, description="The report to generate when the alert triggers" + triggers: Optional[Triggers] = Field( + None, + description=( + "Configuration for actions to trigger for the alert. Available actions are: " + f"{', '.join([arg.__name__ for arg in get_args(get_args(Trigger)[0])])}" + ), ) - triggers: Optional[Triggers] = Field(None, description="The triggers to use for the alert") always_trigger: Optional[bool] = Field( None, description=( - "Whether to always trigger the alert. Default is False. This skips the " - "alert check and run triggers immediately" + "Whether to always trigger the actions when sources are scanned. This skips the " + "check for if the alert conditions are satisfied and run triggers immediately. " + "Defaults to False." ), ) repeat: Optional[bool] = Field( None, description=( "Whether to repeat the alert. Default is True. If False, the alert will be " - "disabled after it triggers once" + "disabled after it triggers once." ), ) active: Optional[bool] = Field( @@ -308,6 +441,21 @@ class UpdateAlertRequest(BaseSchema): "If set, the alert will be disabled after this date." ), ) + report: Optional[ReportRequest] = Field( + None, + description=( + "Configuration for generating a written report when the alert triggers. " + "If not specified, no report is generated." + ), + ) + title: Optional[str] = Field( + None, + description="The title of the alert. If not provided, no title will be used.", + examples=["Alert for US President's statements on the economy"], + ) + share_link: Optional[HttpUrlString] = Field( + None, description="The Newsplunker share link to update when the alert triggers." + ) class AlertLog(BaseModel): diff --git a/asknews_sdk/dto/deepnews.py b/asknews_sdk/dto/deepnews.py index 7ebf89d..ab9b701 100644 --- a/asknews_sdk/dto/deepnews.py +++ b/asknews_sdk/dto/deepnews.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Any, Dict, List, Literal, Optional, Union +from typing import Any, Dict, List, Literal, Optional, Union, get_args +from uuid import UUID from pydantic import AnyUrl, BaseModel, ConfigDict, Discriminator, Field, Tag from typing_extensions import Annotated, TypeAlias @@ -53,71 +54,110 @@ class CreateDeepNewsResponseUsage(BaseModel): total_tokens: Annotated[int, Field(title="Total Tokens")] +DeepNewsSourceType = Literal["asknews", "google", "graph", "wiki", "x", "reddit", "charts"] +DeepNewsSourceTypeDefault: DeepNewsSourceType = "asknews" + +DeepNewsInlineCitationType = Literal["markdown_link", "numbered", "none"] +DeepNewsInlineCitationTypeDefault: DeepNewsInlineCitationType = "markdown_link" + + class CreateDeepNewsRequest(BaseSchema): model_config = ConfigDict( extra="allow", ) - model: Annotated[Optional[str], Field(title="Model")] = "deepseek" - messages: Annotated[List[CreateDeepNewsRequestMessage], Field(title="Messages")] - temperature: Annotated[Optional[float], Field(title="Temperature")] = 0.9 - top_p: Annotated[Optional[float], Field(title="Top P")] = 1.0 - n: Annotated[Optional[int], Field(title="N")] = 1 - stream: Annotated[Optional[bool], Field(title="Stream")] = False - stop: Annotated[Optional[Union[str, List[str]]], Field(title="Stop")] = None - max_tokens: Annotated[Optional[int], Field(title="Max Tokens")] = 9999 - presence_penalty: Annotated[Optional[int], Field(title="Presence Penalty")] = 0 - frequency_penalty: Annotated[Optional[int], Field(title="Frequency Penalty")] = 0 - user: Annotated[Optional[str], Field(title="User")] = None - inline_citations: Annotated[ - Optional[Literal["markdown_link", "numbered", "none"]], - Field(title="Type of inline citation formatting."), - ] = "markdown_link" - journalist_mode: Annotated[ - Optional[bool], + messages: Annotated[ + List[CreateDeepNewsRequestMessage], Field( title=( - "Activate journalist mode, with improved alignment for making claims" - "with supporting evidence. Improved journalistic style." - ), + "The messages to send to DeepNews. Each message should have 'role' and " + "'content' keys. " + "Use this to specify what research/monitoring task DeepNews should perform. The " + "'content' for the final 'user' message should be your Alert query." + ) ), - ] = True - asknews_watermark: Annotated[ - Optional[bool], Field(title='Append "Generated by AskNews AI" watermark') - ] = True - append_references: Annotated[Optional[bool], Field(title="Append References or not")] = True - conversational_awareness: Annotated[Optional[bool], Field(title="Conversational Awareness")] = ( - False - ) + ] + model: Annotated[ + Optional[str], + Field( + title=("The model to use for DeepNews research. Check API reference for default model.") + ), + ] = None filter_params: Annotated[ Optional[Dict[str, Any]], Field(title="Any filter param available on the /news endpoint."), ] = None + search_depth: Annotated[ + Optional[int], + Field( + title=("The search depth for deep research. Higher values mean more thorough research.") + ), + ] = 2 + max_depth: Annotated[Optional[int], Field(title="The maximum research depth allowed.")] = 4 sources: Annotated[ Optional[ Union[ - Literal["asknews", "google", "graph", "wiki", "x", "reddit", "charts"], - List[Literal["asknews", "google", "graph", "wiki", "x", "reddit", "charts"]], + DeepNewsSourceType, + List[DeepNewsSourceType], ] ], - Field(title="Sources"), - ] = "asknews" - search_depth: Annotated[Optional[int], Field(title="Search Depth")] = 2 - max_depth: Annotated[Optional[int], Field(title="Max Depth")] = 4 + Field( + title=( + "Which data sources DeepNews should query. Can be a single source or a list. " + "Available sources are: " + f"{', '.join(get_args(DeepNewsSourceType))}" + ), + ), + ] = DeepNewsSourceTypeDefault + inline_citations: Annotated[ + Optional[DeepNewsInlineCitationType], + Field( + title=( + "How to format inline citations in the response. Defaults to: " + f"{DeepNewsInlineCitationTypeDefault}. Available options: " + f"{', '.join(get_args(DeepNewsInlineCitationType))}" + ) + ), + ] = DeepNewsInlineCitationTypeDefault return_sources: Annotated[ Optional[bool], Field(title="Return all collected sources as objects as the last token of the stream."), ] = True - include_coordinates: Annotated[ + only_cited_sources: Annotated[ Optional[bool], Field( title=( - "Include geocoordinates of the sources in the internal context. " - "Activating this will increase internal token usage. " - "This is useful if you need the LLM to report exact coordinates " - "to you." + "Whether to return only of sources that are cited/referenced in the generated " + "response content." + ) + ), + ] = True + append_references: Annotated[ + Optional[bool], + Field(title="Whether to append a 'Cited Articles' section at the end of the response."), + ] = True + journalist_mode: Annotated[ + Optional[bool], + Field( + title=( + "Activate journalist mode, with improved alignment for making claims" + "with supporting evidence. Improved journalistic style." ), ), + ] = True + conversational_awareness: Annotated[ + Optional[bool], Field(title="Whether to use conversational awareness.") ] = False + include_entities: Annotated[ + Optional[bool], + Field( + title=( + "Include entities of the sources in the internal context. " + "Activating this will increase internal token usage. " + "This is useful if you want the LLM to better understand " + "which entities are associated with which types in the context." + ), + ), + ] = True include_graphs: Annotated[ Optional[bool], Field( @@ -129,17 +169,70 @@ class CreateDeepNewsRequest(BaseSchema): ), ), ] = False - include_entities: Annotated[ + include_coordinates: Annotated[ Optional[bool], Field( title=( - "Include entities of the sources in the internal context. " + "Include geocoordinates of the sources in the internal context. " "Activating this will increase internal token usage. " - "This is useful if you want the LLM to better understand " - "which entities are associated with which types in the context." + "This is useful if you need the LLM to report exact coordinates " + "to you." ), ), + ] = False + asknews_watermark: Annotated[ + Optional[bool], Field(title="Append 'Generated by AskNews AI' watermark") ] = True + stream: Annotated[ + Optional[bool], Field(title="Whether to stream the response as server-sent events.") + ] = False + stop: Annotated[ + Optional[Union[str, List[str]]], + Field(title="Sequence(s) where the model will stop generating further tokens."), + ] = None + temperature: Annotated[ + Optional[float], Field(title="The temperature of the DeepNews agent model.") + ] = 0.9 + top_p: Annotated[ + Optional[float], + Field( + title=( + "Nucleus sampling parameter. Only tokens with cumulative probability up " + "to top_p are considered." + ) + ), + ] = 1.0 + n: Annotated[Optional[int], Field(title="Number of completions to generate.")] = 1 + max_tokens: Annotated[ + Optional[int], Field(title="Maximum number of tokens to generate in the response.") + ] = 9999 + presence_penalty: Annotated[ + Optional[int], + Field( + title=( + "Penalizes new tokens based on whether they appear in the text so far, " + "encouraging new topics." + ) + ), + ] = 0 + frequency_penalty: Annotated[ + Optional[int], + Field( + title=( + "Penalizes new tokens based on their frequency in the text so far, " + "reducing repetition." + ) + ), + ] = 0 + user: Annotated[ + Optional[str], + Field( + title=("A unique identifier for the end-user, useful for tracking and abuse detection.") + ), + ] = None + thread_id: Annotated[ + Optional[UUID], Field(title="ID of an existing thread to continue the conversation.") + ] = None class CreateDeepNewsResponse(BaseSchema):