1616 LinkupTooManyRequestsError ,
1717 LinkupUnknownError ,
1818)
19- from linkup .types import LinkupFetchResponse , LinkupSearchResults , LinkupSourcedAnswer
19+ from linkup .types import (
20+ LinkupFetchResponse ,
21+ LinkupSearchResults ,
22+ LinkupSearchStructuredResponse ,
23+ LinkupSourcedAnswer ,
24+ )
2025
2126
2227class LinkupClient :
@@ -58,6 +63,7 @@ def search(
5863 exclude_domains : Optional [list [str ]] = None ,
5964 include_domains : Optional [list [str ]] = None ,
6065 include_inline_citations : Optional [bool ] = None ,
66+ include_sources : Optional [bool ] = None ,
6167 ) -> Any :
6268 """Perform a web search using the Linkup API `search` endpoint.
6369
@@ -85,13 +91,18 @@ def search(
8591 include_domains: If you want the search to only return results from certain domains.
8692 include_inline_citations: If output_type is "sourcedAnswer", indicate whether the
8793 answer should include inline citations.
94+ include_sources: If output_type is "structured", indicate whether the answer should
95+ include sources. This will modify the schema of the structured response.
8896
8997 Returns:
90- The Linkup API search result. If output_type is "searchResults", the result will be a
91- linkup.LinkupSearchResults. If output_type is "sourcedAnswer", the result will be a
92- linkup.LinkupSourcedAnswer. If output_type is "structured", the result will be
93- either an instance of the provided pydantic.BaseModel, or an arbitrary data
94- structure, following structured_output_schema.
98+ The Linkup API search result, which can have different types based on the parameters:
99+ - LinkupSearchResults if output_type is "searchResults"
100+ - LinkupSourcedAnswer if output_type is "sourcedAnswer"
101+ - the provided pydantic.BaseModel or an arbitrary data structure if output_type is
102+ "structured" and include_sources is False
103+ - LinkupSearchStructuredResponse with the provided pydantic.BaseModel or an arbitrary
104+ data structure as data field, if output_type is "structured" and include_sources is
105+ True
95106
96107 Raises:
97108 TypeError: If structured_output_schema is not provided or is not a string or a
@@ -113,6 +124,7 @@ def search(
113124 exclude_domains = exclude_domains ,
114125 include_domains = include_domains ,
115126 include_inline_citations = include_inline_citations ,
127+ include_sources = include_sources ,
116128 )
117129
118130 response : httpx .Response = self ._request (
@@ -128,6 +140,7 @@ def search(
128140 response = response ,
129141 output_type = output_type ,
130142 structured_output_schema = structured_output_schema ,
143+ include_sources = include_sources ,
131144 )
132145
133146 async def async_search (
@@ -142,6 +155,7 @@ async def async_search(
142155 exclude_domains : Optional [list [str ]] = None ,
143156 include_domains : Optional [list [str ]] = None ,
144157 include_inline_citations : Optional [bool ] = None ,
158+ include_sources : Optional [bool ] = None ,
145159 ) -> Any :
146160 """Asynchronously perform a web search using the Linkup API `search` endpoint.
147161
@@ -169,13 +183,18 @@ async def async_search(
169183 include_domains: If you want the search to only return results from certain domains.
170184 include_inline_citations: If output_type is "sourcedAnswer", indicate whether the
171185 answer should include inline citations.
186+ include_sources: If output_type is "structured", indicate whether the answer should
187+ include sources. This will modify the schema of the structured response.
172188
173189 Returns:
174- The Linkup API search result. If output_type is "searchResults", the result will be a
175- linkup.LinkupSearchResults. If output_type is "sourcedAnswer", the result will be a
176- linkup.LinkupSourcedAnswer. If output_type is "structured", the result will be
177- either an instance of the provided pydantic.BaseModel, or an arbitrary data
178- structure, following structured_output_schema.
190+ The Linkup API search result, which can have different types based on the parameters:
191+ - LinkupSearchResults if output_type is "searchResults"
192+ - LinkupSourcedAnswer if output_type is "sourcedAnswer"
193+ - the provided pydantic.BaseModel or an arbitrary data structure if output_type is
194+ "structured" and include_sources is False
195+ - LinkupSearchStructuredResponse with the provided pydantic.BaseModel or an arbitrary
196+ data structure as data field, if output_type is "structured" and include_sources is
197+ True
179198
180199 Raises:
181200 TypeError: If structured_output_schema is not provided or is not a string or a
@@ -197,6 +216,7 @@ async def async_search(
197216 exclude_domains = exclude_domains ,
198217 include_domains = include_domains ,
199218 include_inline_citations = include_inline_citations ,
219+ include_sources = include_sources ,
200220 )
201221
202222 response : httpx .Response = await self ._async_request (
@@ -212,6 +232,7 @@ async def async_search(
212232 response = response ,
213233 output_type = output_type ,
214234 structured_output_schema = structured_output_schema ,
235+ include_sources = include_sources ,
215236 )
216237
217238 def fetch (
@@ -419,6 +440,7 @@ def _get_search_params(
419440 exclude_domains : Optional [list [str ]],
420441 include_domains : Optional [list [str ]],
421442 include_inline_citations : Optional [bool ],
443+ include_sources : Optional [bool ],
422444 ) -> dict [str , Union [str , bool , list [str ]]]:
423445 params : dict [str , Union [str , bool , list [str ]]] = dict (
424446 q = query ,
@@ -448,6 +470,8 @@ def _get_search_params(
448470 params ["includeDomains" ] = include_domains
449471 if include_inline_citations is not None :
450472 params ["includeInlineCitations" ] = include_inline_citations
473+ if include_sources is not None :
474+ params ["includeSources" ] = include_sources
451475
452476 return params
453477
@@ -471,23 +495,35 @@ def _parse_search_response(
471495 response : httpx .Response ,
472496 output_type : Literal ["searchResults" , "sourcedAnswer" , "structured" ],
473497 structured_output_schema : Union [type [BaseModel ], str , None ],
498+ include_sources : Optional [bool ],
474499 ) -> Any :
475500 response_data : Any = response .json ()
476- output_base_model : Optional [type [BaseModel ]] = None
477501 if output_type == "searchResults" :
478- output_base_model = LinkupSearchResults
502+ return LinkupSearchResults . model_validate ( response_data )
479503 elif output_type == "sourcedAnswer" :
480- output_base_model = LinkupSourcedAnswer
481- elif (
482- output_type == "structured"
483- and not isinstance (structured_output_schema , (str , type (None )))
484- and issubclass (structured_output_schema , BaseModel )
485- ):
486- output_base_model = structured_output_schema
487-
488- if output_base_model is None :
504+ return LinkupSourcedAnswer .model_validate (response_data )
505+ elif output_type == "structured" :
506+ if structured_output_schema is None :
507+ raise ValueError (
508+ "structured_output_schema must be provided when output_type is 'structured'"
509+ )
510+ # HACK: we assume that `include_sources` will default to False, since the API output can
511+ # be arbitrary so we can't guess if it includes sources or not
512+ if include_sources :
513+ if not isinstance (structured_output_schema , str ) and issubclass (
514+ structured_output_schema , BaseModel
515+ ):
516+ response_data ["data" ] = structured_output_schema .model_validate (
517+ response_data ["data" ]
518+ )
519+ return LinkupSearchStructuredResponse .model_validate (response_data )
520+ if not isinstance (structured_output_schema , str ) and issubclass (
521+ structured_output_schema , BaseModel
522+ ):
523+ return structured_output_schema .model_validate (response_data )
489524 return response_data
490- return output_base_model .model_validate (response_data )
525+ else :
526+ raise ValueError (f"Unexpected output_type value: '{ output_type } '" )
491527
492528 def _parse_fetch_response (self , response : httpx .Response ) -> LinkupFetchResponse :
493529 return LinkupFetchResponse .model_validate (response .json ())
0 commit comments