agsagds's picture
refactor: replace requests with httpx for asynchronous HTTP calls and update error handling in app.py
706ded8
Raw
History Blame Contribute Delete
7.35 kB
import asyncio
import os
import datetime
from llama_index.core.workflow import Event, StartEvent, StopEvent, Workflow, step, Context
from llama_index.llms.nebius import NebiusLLM
from agent import BasicAgent
"""
1. Decide what to use: context or event payload?
2. Investigate alternative to initializate llm at each step|global llm
3. Throw relevant data throght steps
"""
class EvalPlanEvent(Event):
planStep: str
class FinalAnswerEvent(Event):
finalAnswer: str
class UpdatePlanEvent(Event):
planStep: str
planStepResult: str
class MultiStepWorkflow(Workflow):
def __init__(self, **kwargs):
self.llm = NebiusLLM(
api_key=os.getenv("NEBIUS_API_KEY"),
model="Qwen/Qwen3-235B-A22B-Instruct-2507",
api_base="https://api.tokenfactory.nebius.com/v1",
system_prompt=f"Today's date is {datetime.datetime.now().strftime('%Y-%m-%d')}."
)
self.agent = BasicAgent(verbose=kwargs.get("verbose", False))
super().__init__(**kwargs)
@step
async def makePlanStep(self, ctx: Context, ev: StartEvent) -> EvalPlanEvent:
if not hasattr(ev, "question"):
raise ValueError("question field is required")
await ctx.store.set("question", ev.question)
plan = await self.llm.acomplete("""Make a plan to answer the question. Plan should be a list of steps.
Each step should contain enough context to execute the step.
Formulate the steps in a way that can be executed by the agent.
Maximum 7 steps. Return only the plan, no other text.
The question: """ + ev.question)
await ctx.store.set("plan", str(plan))
step = await self.llm.acomplete("""Get the first step of the plan. Return only the step, no other text.
The plan: """ + str(plan))
print(f'Plan is {plan}')
return EvalPlanEvent(planStep=str(step))
@step
async def evalPlanStep(self, ctx: Context, ev: EvalPlanEvent) -> UpdatePlanEvent | FinalAnswerEvent:
if not hasattr(ev, "planStep"):
raise ValueError("planStep field is required")
result = await self.agent(f"The question is: {await ctx.store.get('question')} \n\n The plan is: {await ctx.store.get('plan')} \n\n Execute only the step: {ev.planStep}")
return UpdatePlanEvent(planStep=ev.planStep, planStepResult=str(result))
@step
async def updatePlanStep(self, ctx: Context, ev: UpdatePlanEvent)-> EvalPlanEvent | FinalAnswerEvent:
if not hasattr(ev, "planStep"):
raise ValueError("planStep field is required")
if not hasattr(ev, "planStepResult"):
raise ValueError("planStepResult field is required")
plan = await ctx.store.get("plan")
question = await ctx.store.get("question")
plan = await self.llm.acomplete("""Update the plan based on the plan step result.
Note that each plan step should contain enough context to execute the step and formulate the steps in a way that can be executed by the agent.
Maximum 7 steps.
Return only the updated plan, no other text.
The plan step: """ + ev.planStep + """
The plan step result: """ + ev.planStepResult + """
The question: """ + question + """
The plan: """ + plan)
await ctx.store.set("plan", str(plan))
verdict = await self.llm.acomplete("""Check the question and the plan. If there is enough information to answer the question, then return answer with the following template: FINAL ANSWER: [FINAL ANSWER].
Otherwise return an empty string.
The question: """ + question + """
The plan: """ + str(plan))
print(f'Plan is {plan} \n\nVerdict is {verdict}')
if "FINAL ANSWER" in str(verdict):
return FinalAnswerEvent(finalAnswer=str(verdict).split("FINAL ANSWER:")[1])
step = await self.llm.acomplete("""Get the next step to evaluate of the plan. Return only the step, no other text.
The plan: """ + str(plan))
return EvalPlanEvent(planStep=str(step))
@step
async def finalAnswerStep(self, ctx: Context, ev: FinalAnswerEvent) -> StopEvent:
if not hasattr(ev, "finalAnswer"):
raise ValueError("finalAnswer field is required")
question = await ctx.store.get("question")
formattedAnswer = await self.llm.acomplete("""
Help me to format the answer to the correct format. Return only the formatted answer, no other text.
<question>
""" + question + """
</question>
<answer>
""" + ev.finalAnswer + """
</answer>
<formatting rules>
Answer should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.
If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.
If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.
</formatting rules>"""
)
return StopEvent(result=str(formattedAnswer))
if __name__ == "__main__":
async def main():
questionAlbums = "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia."
questionReverse = ".rewsna eht sa \"tfel\" drow eht fo etisoppo eht etirw ,ecnetnes siht dnatsrednu uoy fI"
questionDinosaur = "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?"
questionTable = "Given this table defining * on the set S = {a, b, c, d, e}\n\n|*|a|b|c|d|e|\n|---|---|---|---|---|---|\n|a|a|b|c|b|d|\n|b|b|c|a|e|c|\n|c|c|a|b|b|a|\n|d|b|e|b|e|d|\n|e|d|b|a|d|c|\n\nprovide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order."
questionSurname = "What is the surname of the equine veterinarian mentioned in 1.E Exercises from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023?"
from dotenv import load_dotenv
load_dotenv()
workflow = MultiStepWorkflow(timeout=300, verbose=False)
response = await workflow.run(question=questionSurname)
print(response)
asyncio.run(main())