Skip to content

Commit fc39ebd

Browse files
committed
Add imgflip and fix appolo processors
1 parent 0df5870 commit fc39ebd

File tree

5 files changed

+286
-45
lines changed

5 files changed

+286
-45
lines changed

llmstack/processors/providers/apollo/organization_search.py

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
ApiProcessorInterface,
1212
ApiProcessorSchema,
1313
)
14+
from llmstack.processors.providers.apollo.people_search import APIResponse
1415
from llmstack.processors.providers.metrics import MetricType
1516

1617
logger = logging.getLogger(__name__)
@@ -50,21 +51,22 @@ class OrganizationSearchInput(ApiProcessorSchema):
5051

5152

5253
class OrganizationSearchOutput(ApiProcessorSchema):
53-
response: str = Field(description="The response from the API call as a string", default="")
54-
response_json: Optional[Dict[str, Any]] = Field(
55-
description="The response from the API call as a JSON object", default={}
54+
breadcrumbs: Optional[List[Dict[str, str]]] = Field(description="The breadcrumbs for the API call", default=[])
55+
accounts: Optional[List[Dict[str, Any]]] = Field(description="The list of accounts from the API call", default=[])
56+
organizations: Optional[List[Dict[str, Any]]] = Field(
57+
description="The list of organizations from the API call", default=[]
5658
)
57-
headers: Optional[Dict[str, str]] = Field(description="The headers from the API call", default={})
58-
code: int = Field(description="The status code from the API call", default=200)
59-
size: int = Field(description="The size of the response from the API call", default=0)
60-
time: float = Field(description="The time it took to get the response from the API call", default=0.0)
59+
api_response: APIResponse = Field(description="The response from the API call", default={})
60+
61+
62+
class PeopleSearchOutput(ApiProcessorSchema):
63+
breadcrumbs: Optional[List[Dict[str, str]]] = Field(description="The breadcrumbs for the API call", default=[])
64+
people: Optional[List[Dict[str, Any]]] = Field(description="The list of people from the API call", default=[])
65+
contacts: Optional[List[Dict[str, Any]]] = Field(description="The list of contacts for the API call", default=[])
66+
api_response: APIResponse = Field(description="The response from the API call", default={})
6167

6268

6369
class OrganizationSearchConfiguration(ApiProcessorSchema):
64-
connection_id: Optional[str] = Field(
65-
description="The connection id to use for the API call",
66-
json_schema_extra={"advanced_parameter": False, "widget": "connection"},
67-
)
6870
page: Optional[int] = Field(
6971
description="The page number to return",
7072
default=1,
@@ -101,7 +103,7 @@ def provider_slug() -> str:
101103
@classmethod
102104
def get_output_template(cls) -> OutputTemplate | None:
103105
return OutputTemplate(
104-
markdown="{{response}}",
106+
markdown="{{api_response.response}}",
105107
jsonpath="$.accounts",
106108
)
107109

@@ -114,18 +116,23 @@ def process(self) -> dict:
114116
if not values:
115117
continue
116118

117-
values = [urllib.parse.quote(value) for value in values]
118-
if key == "organization_num_employees_ranges" and values:
119+
values = (
120+
[urllib.parse.quote(value) for value in values]
121+
if isinstance(values, list)
122+
else urllib.parse.quote(str(values))
123+
)
124+
125+
if key == "organization_num_employees_ranges":
119126
query_params_str += f"&organization_num_employees_ranges[]={','.join(values)}"
120-
elif key == "organization_locations" and values:
127+
elif key == "organization_locations":
121128
query_params_str += f"&organization_locations[]={','.join(values)}"
122-
elif key == "organization_not_locations" and values:
129+
elif key == "organization_not_locations":
123130
query_params_str += f"&organization_not_locations[]={','.join(values)}"
124-
elif key == "q_organization_keyword_tags" and values:
131+
elif key == "q_organization_keyword_tags":
125132
query_params_str += f"&q_organization_keyword_tags[]={','.join(values)}"
126-
elif key == "q_organization_name" and values:
133+
elif key == "q_organization_name":
127134
query_params_str += f"&q_organization_name={values}"
128-
elif key == "organization_ids" and values:
135+
elif key == "organization_ids":
129136
query_params_str += f"&organization_ids[]={','.join(values)}"
130137

131138
if query_params_str:
@@ -156,13 +163,18 @@ def process(self) -> dict:
156163

157164
async_to_sync(self._output_stream.write)(
158165
OrganizationSearchOutput(
159-
response=response_text,
160-
response_json=response_json,
161-
response_objref=objref,
162-
headers=dict(response.headers),
163-
code=response.status_code,
164-
size=len(response.text),
165-
time=response.elapsed.total_seconds(),
166+
api_response=APIResponse(
167+
response=response_text,
168+
response_json=response_json,
169+
response_objref=objref,
170+
headers=dict(response.headers),
171+
code=response.status_code,
172+
size=len(response.text),
173+
time=response.elapsed.total_seconds(),
174+
),
175+
breadcrumbs=response_json.get("breadcrumbs", []),
176+
accounts=response_json.get("accounts", []),
177+
organizations=response_json.get("organizations", []),
166178
)
167179
)
168180

llmstack/processors/providers/apollo/people_search.py

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import urllib.parse
23
from typing import Any, Dict, List, Optional
34

45
from asgiref.sync import async_to_sync
@@ -38,8 +39,8 @@ class PeopleSearchInput(ApiProcessorSchema):
3839
)
3940
contact_email_status: Optional[List[str]] = Field(
4041
description="An array of strings to look for people having a set of email statuses",
41-
examples=["verified", "guessed", "unavailable", "bounced", "pending_manual_fulfillment"],
42-
default=["verified", "guessed", "unavailable", "pending_manual_fulfillment"],
42+
examples=["verified", "unverified", "unavailable", "likely to engage"],
43+
default=["verified", "unverified", "unavailable", "likely to engage"],
4344
)
4445
q_organization_domains: Optional[List[str]] = Field(
4546
description="An array of the company domains to search for, joined by the new line character.",
@@ -82,11 +83,6 @@ class PeopleSearchOutput(ApiProcessorSchema):
8283

8384

8485
class PeopleSearchConfiguration(ApiProcessorSchema):
85-
connection_id: Optional[str] = Field(
86-
default=None,
87-
description="The connection id to use for the API call",
88-
json_schema_extra={"widget": "hidden"},
89-
)
9086
max_results: Optional[int] = Field(
9187
description="The maximum number of results to return",
9288
default=10,
@@ -97,9 +93,9 @@ class PeopleSearchConfiguration(ApiProcessorSchema):
9793
)
9894
page: Optional[int] = Field(
9995
description="The page number to return",
100-
default=None,
96+
default=1,
10197
)
102-
page_size: Optional[int] = Field(description="The number of results to return per page", default=None, le=20, ge=10)
98+
page_size: Optional[int] = Field(description="The number of results to return per page", default=10, le=20, ge=10)
10399

104100

105101
class PeopleSearch(ApiProcessorInterface[PeopleSearchInput, PeopleSearchOutput, PeopleSearchConfiguration]):
@@ -131,20 +127,47 @@ def get_output_template(cls) -> OutputTemplate | None:
131127
)
132128

133129
def process(self) -> dict:
134-
data = self._input.model_dump()
135-
data["page"] = self._config.page or 1
136-
data["per_page"] = self._config.page_size or self._config.max_results or 10
137-
138-
for key in data:
139-
if not data[key]:
140-
data[key] = None
141-
142130
provider_config = self.get_provider_config(provider_slug=self.provider_slug())
143131
api_key = provider_config.api_key
144132

133+
query_params_str = ""
134+
for key, values in self._input.model_dump().items():
135+
if not values:
136+
continue
137+
138+
values = (
139+
[urllib.parse.quote(value) for value in values]
140+
if isinstance(values, list)
141+
else urllib.parse.quote(str(values))
142+
)
143+
144+
if key == "q_keywords":
145+
query_params_str += f"&q_keywords={','.join(values)}"
146+
elif key == "person_locations":
147+
query_params_str += f"&person_locations[]={','.join(values)}"
148+
elif key == "person_seniorities":
149+
query_params_str += f"&person_seniorities[]={','.join(values)}"
150+
elif key == "contact_email_status":
151+
query_params_str += f"&contact_email_status[]={','.join(values)}"
152+
elif key == "q_organization_domains":
153+
query_params_str += f"&q_organization_domains={','.join(values)}"
154+
elif key == "organization_locations":
155+
query_params_str += f"&organization_locations[]={','.join(values)}"
156+
elif key == "organization_ids":
157+
query_params_str += f"&organization_ids[]={','.join(values)}"
158+
elif key == "organization_num_employees_ranges":
159+
query_params_str += f"&organization_num_employees_ranges[]={','.join(values)}"
160+
elif key == "person_titles":
161+
query_params_str += f"&person_titles[]={','.join(values)}"
162+
163+
if query_params_str:
164+
query_params_str = "?" + query_params_str[1:]
165+
166+
query_url = f"https://api.apollo.io/v1/mixed_people/search{query_params_str}?page={self._config.page}&per_page{self._config.page_size}"
167+
168+
logger.info(f"Querying Apollo API with URL: {query_url}")
145169
response = prequests.post(
146-
url="https://api.apollo.io/v1/mixed_people/search",
147-
json=data,
170+
url=query_url,
148171
headers={"Cache-Control": "no-cache", "Content-Type": "application/json", "X-Api-Key": api_key},
149172
)
150173
if response.ok:
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from pydantic import Field
2+
3+
from llmstack.processors.providers.config import ProviderConfig
4+
5+
6+
class ImgFlipProviderConfig(ProviderConfig):
7+
provider_slug: str = "imgflip"
8+
username: str = Field(
9+
title="Username",
10+
default="",
11+
description="Username for the ImgFlip API",
12+
json_schema_extra={"widget": "text", "advanced_parameter": False},
13+
)
14+
password: str = Field(
15+
title="Password",
16+
default="",
17+
description="Password for the ImgFlip API",
18+
json_schema_extra={"widget": "password", "advanced_parameter": False},
19+
)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import logging
2+
from typing import Any, Dict, Optional
3+
4+
from asgiref.sync import async_to_sync
5+
from pydantic import Field
6+
7+
from llmstack.apps.schemas import OutputTemplate
8+
from llmstack.common.utils.prequests import post
9+
from llmstack.processors.providers.api_processor_interface import (
10+
ApiProcessorInterface,
11+
ApiProcessorSchema,
12+
)
13+
from llmstack.processors.providers.metrics import MetricType
14+
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class CaptionImageProcessorInput(ApiProcessorSchema):
19+
template_id: Optional[str] = Field(description="Template Id", default=None)
20+
top_text: Optional[str] = Field(description="The text to put on the top of the image", default=None)
21+
bottom_text: Optional[str] = Field(description="The text to put on the bottom of the image", default=None)
22+
23+
24+
class CaptionImageProcessorOutput(ApiProcessorSchema):
25+
response: str = Field(description="The response from the API call as a string", default="")
26+
response_json: Optional[Dict[str, Any]] = Field(
27+
description="The response from the API call as a JSON object", default={}
28+
)
29+
response_objref: Optional[str] = Field(description="The reference to the response object", default=None)
30+
headers: Optional[Dict[str, str]] = Field(description="The headers from the API call", default={})
31+
code: int = Field(description="The status code from the API call", default=200)
32+
size: int = Field(description="The size of the response from the API call", default=0)
33+
time: float = Field(description="The time it took to get the response from the API call", default=0.0)
34+
35+
36+
class CaptionImageProcessorConfiguration(ApiProcessorSchema):
37+
pass
38+
39+
40+
class CaptionImageProcessor(
41+
ApiProcessorInterface[CaptionImageProcessorInput, CaptionImageProcessorOutput, CaptionImageProcessorConfiguration],
42+
):
43+
"""
44+
Caption an image.
45+
"""
46+
47+
@staticmethod
48+
def name() -> str:
49+
return "Caption an image"
50+
51+
@staticmethod
52+
def slug() -> str:
53+
return "caption_image"
54+
55+
@staticmethod
56+
def description() -> str:
57+
return "Caption an image."
58+
59+
@staticmethod
60+
def provider_slug() -> str:
61+
return "imgflip"
62+
63+
@classmethod
64+
def get_output_template(cls) -> OutputTemplate | None:
65+
return OutputTemplate(
66+
markdown="{{response}}",
67+
jsonpath="$.response",
68+
)
69+
70+
async def process(self) -> dict:
71+
provider_config = self.get_provider_config(provider_slug=self.provider_slug(), processor_slug="*")
72+
deployment_config = self.get_provider_config(provider_slug=self.provider_slug(), processor_slug="*")
73+
74+
username = deployment_config.username
75+
password = deployment_config.password
76+
template_id = self._input.template_id
77+
text0 = self._input.top_text
78+
text1 = self._input.bottom_text
79+
80+
response = post(
81+
url="https://api.imgflip.com/caption_image",
82+
params={
83+
"template_id": template_id,
84+
"username": username,
85+
"password": password,
86+
"boxes[0][text]": text0,
87+
"boxes[1][text]": text1,
88+
},
89+
)
90+
async_to_sync(self._output_stream.write)(
91+
CaptionImageProcessorOutput(
92+
response=response.text,
93+
response_json=response.json(),
94+
code=response.status_code,
95+
size=len(response.text),
96+
time=response.elapsed.total_seconds(),
97+
)
98+
)
99+
self._usage_data.append(
100+
(
101+
f"{self.provider_slug()}/*/*/*",
102+
MetricType.API_INVOCATION,
103+
(provider_config.provider_config_source, 1),
104+
)
105+
)
106+
return self._output_stream.output.finalize()

0 commit comments

Comments
 (0)