chore: import upstream snapshot with attribution
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
Flake8 Lint / flake8 (push) Has been cancelled
Spell check CI / Spell_Check (push) Has been cancelled
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
Flake8 Lint / flake8 (push) Has been cancelled
Spell check CI / Spell_Check (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
import bs4
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
from typing_extensions import Never
|
||||
|
||||
from agent_framework import Agent, Executor, WorkflowBuilder, WorkflowContext, handler
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
load_dotenv()
|
||||
|
||||
_SUMMARIZE_SYSTEM_PROMPT = """\
|
||||
Please summarize the following text in one paragraph. 100 words.
|
||||
Do not add any information that is not in the text."""
|
||||
|
||||
_CLASSIFY_SYSTEM_PROMPT = """\
|
||||
Your task is to classify a given url into one of the following categories:
|
||||
Movie, App, Academic, Channel, Profile, PDF or None based on the text content information.
|
||||
The classification will be based on the url, the webpage text content summary, or both."""
|
||||
|
||||
_EXAMPLES = [
|
||||
{
|
||||
"url": "https://play.google.com/store/apps/details?id=com.spotify.music",
|
||||
"text_content": (
|
||||
"Spotify is a free music and podcast streaming app with millions of songs, albums, and "
|
||||
"original podcasts. It also offers audiobooks, so users can enjoy thousands of stories. "
|
||||
"It has a variety of features such as creating and sharing music playlists, discovering "
|
||||
"new music, and listening to popular and exclusive podcasts. It also has a Premium "
|
||||
"subscription option which allows users to download and listen offline, and access "
|
||||
"ad-free music. It is available on all devices and has a variety of genres and artists "
|
||||
"to choose from."
|
||||
),
|
||||
"category": "App",
|
||||
"evidence": "Both",
|
||||
},
|
||||
{
|
||||
"url": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw",
|
||||
"text_content": (
|
||||
"NFL Sunday Ticket is a service offered by Google LLC that allows users to watch NFL "
|
||||
"games on YouTube. It is available in 2023 and is subject to the terms and privacy policy "
|
||||
"of Google LLC. It is also subject to YouTube's terms of use and any applicable laws."
|
||||
),
|
||||
"category": "Channel",
|
||||
"evidence": "URL",
|
||||
},
|
||||
{
|
||||
"url": "https://arxiv.org/abs/2303.04671",
|
||||
"text_content": (
|
||||
"Visual ChatGPT is a system that enables users to interact with ChatGPT by sending and "
|
||||
"receiving not only languages but also images, providing complex visual questions or "
|
||||
"visual editing instructions, and providing feedback and asking for corrected results. "
|
||||
"It incorporates different Visual Foundation Models and is publicly available. Experiments "
|
||||
"show that Visual ChatGPT opens the door to investigating the visual roles of ChatGPT with "
|
||||
"the help of Visual Foundation Models."
|
||||
),
|
||||
"category": "Academic",
|
||||
"evidence": "Text content",
|
||||
},
|
||||
{
|
||||
"url": "https://ab.politiaromana.ro/",
|
||||
"text_content": "There is no content available for this text.",
|
||||
"category": "None",
|
||||
"evidence": "None",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _fetch_text_content_from_url(url: str) -> str:
|
||||
try:
|
||||
headers = {
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35"
|
||||
)
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
soup = bs4.BeautifulSoup(response.text, "html.parser")
|
||||
return soup.get_text()[:2000]
|
||||
else:
|
||||
return "No available content"
|
||||
except Exception:
|
||||
return "No available content"
|
||||
|
||||
|
||||
def _format_examples() -> str:
|
||||
parts = []
|
||||
for ex in _EXAMPLES:
|
||||
parts.append(
|
||||
f'URL: {ex["url"]}\n'
|
||||
f'Text content: {ex["text_content"]}\n'
|
||||
f'OUTPUT:\n'
|
||||
f'{{"category": "{ex["category"]}", "evidence": "{ex["evidence"]}"}}\n'
|
||||
)
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
class FetchAndSummarizeExecutor(Executor):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
client = OpenAIChatClient(
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
self._agent = Agent(
|
||||
client=client,
|
||||
name="SummarizeAgent",
|
||||
instructions=_SUMMARIZE_SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
@handler
|
||||
async def process(self, url: str, ctx: WorkflowContext[str]) -> None:
|
||||
text_content = _fetch_text_content_from_url(url)
|
||||
response = await self._agent.run(f"Text: {text_content}\nSummary:")
|
||||
await ctx.send_message(response.text)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClassifyInput:
|
||||
url: str
|
||||
summary: str
|
||||
|
||||
|
||||
class ClassifyExecutor(Executor):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
client = OpenAIChatClient(
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
self._agent = Agent(
|
||||
client=client,
|
||||
name="ClassifyAgent",
|
||||
instructions=_CLASSIFY_SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
@handler
|
||||
async def classify(self, summary: str, ctx: WorkflowContext[Never, dict]) -> None:
|
||||
examples_text = _format_examples()
|
||||
prompt = (
|
||||
f'The selection range of the value of "category" must be within '
|
||||
f'"Movie", "App", "Academic", "Channel", "Profile", "PDF" and "None".\n'
|
||||
f'The selection range of the value of "evidence" must be within '
|
||||
f'"Url", "Text content", and "Both".\n'
|
||||
f"Here are a few examples:\n{examples_text}\n"
|
||||
f"For a given URL and text content, classify the url to complete the "
|
||||
f"category and indicate evidence:\n"
|
||||
f"URL: (see text content)\n"
|
||||
f"Text content: {summary}.\nOUTPUT:"
|
||||
)
|
||||
response = await self._agent.run(prompt)
|
||||
try:
|
||||
result = json.loads(response.text)
|
||||
except Exception:
|
||||
result = {"category": "None", "evidence": "None"}
|
||||
await ctx.yield_output(result)
|
||||
|
||||
|
||||
def create_workflow():
|
||||
"""Create a fresh workflow instance.
|
||||
|
||||
MAF workflows do not support concurrent execution, so each
|
||||
concurrent caller needs its own workflow instance.
|
||||
"""
|
||||
_fetch_summarize = FetchAndSummarizeExecutor(id="fetch_and_summarize")
|
||||
_classify = ClassifyExecutor(id="classify")
|
||||
return (
|
||||
WorkflowBuilder(name="WebClassificationWorkflow", start_executor=_fetch_summarize)
|
||||
.add_edge(_fetch_summarize, _classify)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
workflow = create_workflow()
|
||||
url = "https://play.google.com/store/apps/details?id=com.twitter.android"
|
||||
result = await workflow.run(url)
|
||||
output = result.get_outputs()[0]
|
||||
print(f"Category: {output.get('category')}")
|
||||
print(f"Evidence: {output.get('evidence')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user