but I'm not sure if controlflow sees that or needs...
# marvin-ai
d
but I'm not sure if controlflow sees that or needs or uses that?
n
hi @Dave Aitel - can you explain the context in which you're trying to use/define return types? most prominently you can provide
result_type=T
to tasks
d
Just as an example, my PythonRunner tool looks like this:
Copy code
class PythonRunnerInput(BaseModel):
    code: str = Field(..., description="The Python code to execute")

class PythonRunnerTool(BaseTool):
    name = "PythonRunnerTool"
    description = "Tool to execute Python code on our local box (not the target). Returns the STDOUT and STDERR from the executed Python. You have to print things out to see them!"
    args_schema = PythonRunnerInput

    def __init__(self):
        super().__init__()
        self.args_schema = PythonRunnerInput
but in Control Flow I do this: class ReconReport(BaseModel): report: str
Normally you would assign some "helper" information to the return type, I assume?
Like, something that tells the LLM what you expect in the report?
n
do you have to define tools this way? typically I find normal functions more intuituve
Copy code
def some_tool_that_runs_python(code: str) -> str:
   """useful description for llm"""
   # impl of tool
d
I mean, even in that case, how would you want to tell the LLM what "code" is
is it guessing based on the name of the variable?
n
you can use
Annotated
on
str
or yeah change in the input type to wrap it in a basemodel subclass
d
But to answer your question - I don't have to use LangChain tools, but I have a bunch of them and I wanted to integrate šŸ™‚
I just started ControlFlow testing yesterday
šŸ‘ 1
I might add a sleep in flow_engine.py::start() so I stop getting the timeout connecting to the Prefect API Server
n
or you could just start a prefect server in the background šŸ™‚
Copy code
docker run -p 4200:4200 -d --rm prefecthq/prefect:3.0.0rc17-python3.12 -- prefect --no-prompt server start --host 0.0.0.0
d
Is there an example of adding Annotated to a return type I can copy?
sorry, result_type
I think to repro this timeout maybe it helps to be in a tiny crappy VM doing your dev šŸ™‚
I only allocated 4 cores šŸ™‚
n
hm looks like we need to allow
_AnnotatedAlias
to be provided there, but actually i forgot there's the
instructions
kwarg anyways, so we dont need
Annotated
really
Copy code
In [3]: Task("give me a joke", result_type=str, instructions="mitch hedburg style").run()
╭─ Agent: Marvin ──────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                  │
│  I used to do drugs. I still do, but I used to, too.                                             │
│                                                                                                  │
│  Would you like to hear another one?                                                             │
│                                                                                                  │
╰──────────────────────────────────────────────────────────────────────────────────── 12:23:09 PM ─╯
╭─ Agent: Marvin ──────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                  │
│  āœ… Tool call: "mark_task_17b0d99a_successful"                                                   │
│                                                                                                  │
│     Tool args: {'result': 'I used to do drugs. I still do, but I used to, too.'}                 │
│                                                                                                  │
│     Tool result: Task 17b0d99a ("give me a joke") marked successful.                             │
│                                                                                                  │
╰──────────────────────────────────────────────────────────────────────────────────── 12:23:10 PM ─╯
Out[3]: 'I used to do drugs. I still do, but I used to, too.'
where instead of annotating
str
with
mitch hedburg style
, i just put that in the instructions so maybe instead you say like
valid python code
or whatever you want your string to be?
if you were curious, it should work like this
Copy code
In [4]: Task("give me a joke", result_type=Annotated[str, Field(description="mitch hedburg style")]).run()
d
So in my case since I create a class:
class ReconReport(BaseModel): report: Annotated[str, Field(description="The report for the next agent that descibes the website")]
Does ControlFlow actually read that data and automatically pass it to the LLM?
I don't know if "instructions" and Annotations are really the same thing?
Like I feel like one is more global instructions and the other is micromanaging the LLM to produce the right results ? šŸ™‚
n
yeah i would say whether or not they're functionally equivalent depends on what your task is doing but
Annotated
should work if you want to stick to the wrapper class
Copy code
In [1]: from controlflow import Task

In [2]: from pydantic import BaseModel, Field

In [3]: from typing import Annotated

In [4]: class Recon(BaseModel):
   ...:     report: Annotated[str, Field(description="report for next agent - talk like a pirate")]
   ...:

In [5]: Task("produce a report", result_type=Recon).run()
╭─ Agent: Marvin ──────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                  │
│  Here is the report in a pirate style:                                                           │
│                                                                                                  │
│  ──────────────────────────────────────────────────────────────────────────────────────────────  │
│  Ahoy, matey! Here's th' report ye be needin'.                                                   │
│                                                                                                  │
│  ──────────────────────────────────────────────────────────────────────────────────────────────  │
│  Now, I'll mark this task as successful.                                                         │
│                                                                                                  │
│                                                                                                  │
│  āœ… Tool call: "mark_task_bbff7816_successful"                                                   │
│                                                                                                  │
│     Tool args: {'result': {'report': "Ahoy, matey! Here's th' report ye be needin'."}}           │
│                                                                                                  │
│     Tool result: Task bbff7816 ("produce a report") marked successful.                           │
│                                                                                                  │
╰──────────────────────────────────────────────────────────────────────────────────── 12:33:40 PM ─╯
Out[5]: Recon(report="Ahoy, matey! Here's th' report ye be needin'.")
šŸ™Œ 1
āœ… 1
but at that point, there's no strong reason to use
Annotated
bc you could just set the value to
Field(description="...")
and itd be the same thing. annotated is more nice when you want to plug in a builtin type with some extra metadata someplace