Humanlayer (#19)

* black/ruff still fighting

* wip

* links and rename

* wip to humanlayer

* humanlayer
This commit is contained in:
Dex
2024-08-13 22:27:55 -05:00
committed by GitHub
parent e893c6d939
commit 8114da9251
51 changed files with 201 additions and 182 deletions

View File

@@ -20,6 +20,15 @@ repos:
rev: "v3.0.3"
hooks:
- id: prettier
- repo: https://github.com/tcort/markdown-link-check
rev: "v3.12.2"
hooks:
- id: markdown-link-check
args:
[
--quiet,
--ignore=https://platform.openai.com/docs/guides/function-calling,
]
# - repo: https://github.com/psf/black-pre-commit-mirror
# rev: 24.8.0
# hooks:

View File

@@ -1,4 +1,4 @@
# Contributing to FunctionLayer
# Contributing to HumanLayer
If you're looking to contribute, please:

View File

@@ -24,7 +24,7 @@ RUN --mount=type=cache,target=/root/.cache/pypoetry/cache \
poetry install --no-interaction --no-ansi --without dev
# Copy Python code to the Docker image
COPY functionlayer-core /code/functionlayer-core
COPY humanlayer /code/humanlayer
ENTRYPOINT ["functionlayer"]

View File

@@ -1,6 +1,6 @@
Apache Software License 2.0
Copyright (c) 2024, functionlayer Authors
Copyright (c) 2024, humanlayer Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -69,4 +69,4 @@ test-examples:
:
: 🧠 OpenAI
:
docker compose -f examples/openai_client/docker-compose.yaml run examples
docker compose -f examples/openai_client/docker-compose.yaml run examples

View File

@@ -1,12 +1,12 @@
<div align="center">
![Logo of functionlayer, two diamonds with a plus sign](./docs/functionlayer_logo.png)
![Wordmark Logo of HumanLayer](./docs/images/humanlayer-logo.png)
# **FunctionLayer**
# **HumanLayer**
</div>
**FunctionLayer**: A python toolkit to enable AI agents to communicate with humans in tool-based and asynchronous workflows. By incorporating humans-in-the-loop, agentic tools can be given access to much more powerful and meaningful tool calls and tasks.
**HumanLayer**: A python toolkit to enable AI agents to communicate with humans in tool-based and asynchronous workflows. By incorporating humans-in-the-loop, agentic tools can be given access to much more powerful and meaningful tool calls and tasks.
Bring your LLM (OpenAI, Llama, Claude, etc) and Framework (LangChain, CrewAI, etc) and start giving your AI agents safe access to the world.
@@ -14,11 +14,11 @@ Bring your LLM (OpenAI, Llama, Claude, etc) and Framework (LangChain, CrewAI, et
<h3>
[Homepage](https://www.functionlayer.ai/) | [Get Started](./docs/getting-started.md) | [Discord](https://discord.gg/KNATT2xK) | [Documentation](./docs) | [Examples](./examples)
[Homepage](https://www.humanlayer.dev/) | [Get Started](./docs/getting-started.md) | [Discord](https://discord.gg/KNATT2xK) | [Documentation](./docs) | [Examples](./examples)
</h3>
[![GitHub Repo stars](https://img.shields.io/github/stars/functionlayer/functionlayer)](https://github.com/functionlayer/functionlayer)
[![GitHub Repo stars](https://img.shields.io/github/stars/humanlayer/humanlayer)](https://github.com/humanlayer/humanlayer)
[![License: Apache-2](https://img.shields.io/badge/License-Apache-green.svg)](https://opensource.org/licenses/Apache-2)
</div>
@@ -26,7 +26,7 @@ Bring your LLM (OpenAI, Llama, Claude, etc) and Framework (LangChain, CrewAI, et
## Table of contents
- [Getting Started](#getting-started)
- [Why FunctionLayer?](#why-functionlayer)
- [Why HumanLayer?](#why-humanlayer)
- [Key Features](#key-features)
- [Examples](#examples)
- [Roadmap](#roadmap)
@@ -37,29 +37,29 @@ Bring your LLM (OpenAI, Llama, Claude, etc) and Framework (LangChain, CrewAI, et
To get started, check out [Getting Started](./docs/getting-started.md), watch the [2:30 Getting Started Video](https://www.loom.com/share/97ead4e4a0b84b3dac4fec2ff1b410bf), or jump straight into one of the [Examples](./examples/):
- 🦜⛓️ [LangChain](./examples/langchain/math_example.py)
- 🦜⛓️ [LangChain](./examples/langchain/01-math_example.py)
- 🚣‍ [CrewAI](./examples/crewai/crewai_math.py)
- 🦾 [ControlFlow](./examples/controlflow/controlflow_math.py)
- 🧠 [Raw OpenAI Client](./examples/openai_client/math_example.py)
```shell
pip install functionlayer-ai
pip install humanlayer
```
or for the bleeding edge
```shell
pip install git+https://github.com/functionlayer/functionlayer
pip install git+https://github.com/humanlayer/humanlayer-ai-llm-agents
```
Set `FUNCTIONLAYER_API_TOKEN` and wrap your AI function in `require_approval()`
Set `HUMANLAYER_API_TOKEN` and wrap your AI function in `require_approval()`
```python
from functionlayer import ApprovalMethod, FunctionLayer
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD) # or CLI
from humanlayer import ApprovalMethod, HumanLayer
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD) # or CLI
@fl.require_approval()
@hl.require_approval()
def send_email(to: str, subject: str, body: str):
"""Send an email to the customer"""
...
@@ -77,9 +77,9 @@ Then you can start manging LLM actions in slack, email, or whatever channel you
<div align="center"><img style="width: 400px" alt="A screenshot of slack showing a human replying to the bot" src="./docs/images/slack_approval_response.png"></div>
Check out the [FunctionLayer Docs](./docs/) and the [Getting Started Guide](./docs/getting-started.md) for more information.
Check out the [HumanLayer Docs](./docs/) and the [Getting Started Guide](./docs/getting-started.md) for more information.
## Why FunctionLayer?
## Why HumanLayer?
Functions and tools are a key part of [Agentic Workflows](https://www.deeplearning.ai/the-batch/how-agents-can-improve-llm-performance). They enable LLMs to interact meaningfully with the outside world and automate broad scopes of impactful work. Correct and accurate function calling is essential for AI agents that do meaningful things like book appointments, interact with customers, manage billing information, write+execute code, and more.
@@ -105,30 +105,30 @@ To better define what is meant by "high stakes", some examples:
The high stakes functions are the ones that are the most valuable and promise the most impact in automating away human workflows. The sooner teams can get Agents reliably and safely calling these tools, the sooner they can reap massive benefits.
FunctionLayer provides a set of tools to _deterministically_ guarantee human oversight of high stakes function calls. Even if the LLM makes a mistake or hallucinates, FunctionLayer is baked into the tool/function itself, guaranteeing a human in the loop.
HumanLayer provides a set of tools to _deterministically_ guarantee human oversight of high stakes function calls. Even if the LLM makes a mistake or hallucinates, HumanLayer is baked into the tool/function itself, guaranteeing a human in the loop.
<div align="center"><img style="width: 400px" alt="Function Layer @require_approval decorator wrapping the Commnicate on my behalf function" src="./docs/images/function_layer_require_approval.png"></div>
<div align="center">
<h3><blockquote>
FunctionLayer provides a set of tools to *deterministically* guarantee human oversight of high stakes function calls
HumanLayer provides a set of tools to *deterministically* guarantee human oversight of high stakes function calls
</blockquote></h3>
</div>
## Key Features
- **Require Human Approval for Function Calls**: the `@fl.require_approval()` decorator blocks specifc function calls until a human has been consulted - upon denial, feedback will be passed to the LLM
- **Require Human Approval for Function Calls**: the `@hl.require_approval()` decorator blocks specifc function calls until a human has been consulted - upon denial, feedback will be passed to the LLM
- **Human as Tool**: generic `fl.human_as_tool()` allows for contacting a human for answers, advice, or feedback
- **OmniChannel Contact**: Contact humans and collect responses across Slack, Email, Discord, and more
- **Granular Routing**: Route approvals to specific teams or individuals
- **Bring your own LLM + Framework**: Because FunctionLayer is implemented at tools layer, it supports any LLM and all major orchestration frameworks that support tool calling.
- **Bring your own LLM + Framework**: Because HumanLayer is implemented at tools layer, it supports any LLM and all major orchestration frameworks that support tool calling.
## Examples
You can test different real life examples of FunctionLayer in the [examples folder](./examples/):
You can test different real life examples of HumanLayer in the [examples folder](./examples/):
- 🦜⛓️ [LangChain Math](./examples/langchain/math_example.py)
- 🦜⛓️ [LangChain Human As Tool](./examples/langchain/human_as_tool.py)
- 🦜⛓️ [LangChain Math](./examples/langchain/01-math_example.py)
- 🦜⛓️ [LangChain Human As Tool](./examples/langchain/03-human_as_tool.py)
- 🚣‍ [CrewAI Math](./examples/crewai/crewai_math.py)
- 🦾 [ControlFlow Math](./examples/controlflow/controlflow_math.py)
- 🧠 [Raw OpenAI Client](./examples/openai_client/math_example.py)
@@ -154,8 +154,8 @@ You can test different real life examples of FunctionLayer in the [examples fold
## Contributing
FunctionLayer is open-source and we welcome contributions in the form of issues, documentation, pull requests, and more. See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
HumanLayer is open-source and we welcome contributions in the form of issues, documentation, pull requests, and more. See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
## License
The FunctionLayer SDK in this repo is licensed under the Apache 2 License.
The HumanLayer SDK in this repo is licensed under the Apache 2 License.

View File

@@ -1,4 +1,4 @@
deps = [
'functionlayer/',
"humanlayer/",
]
local_resource('check', cmd='make check test', deps=deps, ignore='**/*pyc*')
local_resource("check", cmd="make check test", deps=deps, ignore="**/*pyc*")

View File

@@ -11,7 +11,7 @@ pip install functionlayer-ai
Or, for the bleeding edge:
```bash
pip install git+https://github.com/functionlayer/functionlayer
pip install git+https://github.com/humanlayer/humanlayer
```
### Choose an LLM Client and Write your first tool-calling agent
@@ -20,10 +20,10 @@ You'll need a short python script that defines a function that you want to gate
and an agent+prompt that will execute the function.
There are a number of different examples you can start from, including:
- 🦜⛓️ [LangChain Math](./examples/langchain/math_example.py)
- 🚣‍ [CrewAI Math](./examples/crewai/crewai_math.py)
- 🦾 [ControlFlow Math](./examples/controlflow/controlflow_math.py)
- 🧠 [Raw OpenAI Client](./examples/openai_client/math_example.py)
- 🦜⛓️ [LangChain Math](../examples/langchain/01-math_example.py)
- 🚣‍ [CrewAI Math](../examples/crewai/crewai_math.py)
- 🦾 [ControlFlow Math](../examples/controlflow/controlflow_math.py)
- 🧠 [Raw OpenAI Client](../examples/openai_client/math_example.py)
We'll use LangChain and OpenAI here since it's fairly succinct for this use case.
@@ -105,7 +105,7 @@ The result of multiplying 2 and 5, then adding 32 to the result, is 42.
```
### Wrap a function with functionlayer
### Wrap a function with humanlayer
Once you've run the example and verified it works, it's time to wrap the function with FunctionLayer.
Add the following to the top of your file:
@@ -116,8 +116,8 @@ from langchain.agents import AgentType
from langchain.tools import tool
from langchain_openai import ChatOpenAI
+ from functionlayer import FunctionLayer
+ fl = FunctionLayer()
+ from humanlayer import HumanLayer
+ hl = HumanLayer()
@tool
@@ -132,11 +132,11 @@ def multiply(x: int, y: int) -> int:
return x * y
```
and then wrap your `multiply` function with `@fl.require_approval()`:
and then wrap your `multiply` function with `@hl.require_approval()`:
```diff
from functionlayer import FunctionLayer
fl = FunctionLayer()
from humanlayer import HumanLayer
hl = HumanLayer()
@tool
@@ -146,7 +146,7 @@ def add(x: int, y: int) -> int:
@tool
+ @fl.require_approval()
+ @hl.require_approval()
def multiply(x: int, y: int) -> int:
"""multiply two numbers"""
return x * y
@@ -172,7 +172,7 @@ def add(x: int, y: int) -> int:
@tool
@fl.require_approval()
@hl.require_approval()
def multiply(x: int, y: int) -> int:
"""multiply two numbers"""
return x * y
@@ -268,7 +268,7 @@ Navigate to the Approval Queue and click the Status button to approve the functi
### Connect Slack
Web approvals are a start, but FunctionLayer really shines when you connect your slack instance. See [configuring slack](./configuring_slack.md) for more information on how to do this, and then you can
Web approvals are a start, but FunctionLayer really shines when you connect your slack instance. See [configuring slack](./configuring-slack.md) for more information on how to do this, and then you can
### Try a Human as tool

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -18,7 +18,7 @@ Table of Contents:
- [Features](#features)
- [Function Decorators](#function-decorators)
- [Human as Tool](#human-as-tool)
- [Functionlayer CLI](#functionlayer-cli)
- [Functionlayer CLI](#cli-mode)
- [Functionlayer Cloud](#functionlayer-cloud)
- [Reference](#reference)
@@ -26,12 +26,12 @@ Table of Contents:
**[Getting Started Guide](./getting-started.md)**
To get started, check out [Getting Started](./getting-started.md) or jump straight into one of the [Examples](../examples/):
To get started, check out [Getting Started](./getting-started.md) or jump straight into one of the [Examples](../examples):
- 🦜⛓️ [LangChain](./examples/langchain/math_example.py)
- 🚣‍ [CrewAI](./examples/crewai/crewai_math.py)
- 🦾 [ControlFlow](./examples/controlflow/controlflow_math.py)
- 🧠 [Raw OpenAI Client](./examples/openai_client/math_example.py)
- 🦜⛓️ [LangChain](../examples/langchain/)
- 🚣‍ [CrewAI](../examples/crewai/)
- 🦾 [ControlFlow](../examples/controlflow/)
- 🧠 [Raw OpenAI Client](../examples/openai_client/)
## Concepts
@@ -98,7 +98,7 @@ The High stakes functions are the ones that are the most valuable and promise th
FunctionLayer provides a set of decorators that can be used to add approval requirements to specific functions.
```python
@fl.require_approval()
@hl.require_approval()
def send_email(to: str, subject: str, body: str):
"""Send an email to the customer"""
...
@@ -119,7 +119,7 @@ In addition to gating function calls, FunctionLayer can also provide a more gene
```python
from functionlayer import ApprovalMethod, ContactChannel, FunctionLayer, SlackContactChannel
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
customer_success_direct_message = ContactChannel(
slack=SlackContactChannel(
@@ -185,17 +185,17 @@ fl_sales = FunctionLayer(
contact_channel=dm_sales,
)
@fl_default.require_approval() # uses project default contact channel
@hl_default.require_approval() # uses project default contact channel
def send_email(to: str, subject: str, body: str):
"""Send an email to the customer"""
...
@fl_sales.require_approval() # uses the sales contact channel
@hl_sales.require_approval() # uses the sales contact channel
def update_crm_record(opportunity_id: str, status: str):
"""Update the CRM record for the opportunity"""
...
@fl_default.require_approval(dm_engineering) # uses the engineering contact channel
@hl_default.require_approval(dm_engineering) # uses the engineering contact channel
def drop_production_database(database_name: str):
"""Drop the specified database"""
...
@@ -230,9 +230,9 @@ import os
from functionlayer import FunctionLayer
fl = FunctionLayer(api_token=os.getenv("FUNCTIONLAYER_API_TOKEN"))
hl = HumanLayer(api_token=os.getenv("FUNCTIONLAYER_API_TOKEN"))
@fl.require_approval()
@hl.require_approval()
def send_email_to_customer(email: str, subject: str, body: str) -> str:
"""
send an email to the customer
@@ -257,7 +257,7 @@ head_of_sales = ContactChannel(
)
)
@fl.require_approval(head_of_sales)
@hl.require_approval(head_of_sales)
def send_email_to_customer(email: str, subject: str, body: str) -> str:
...
```
@@ -268,7 +268,7 @@ In addition to blocking function execution based on human feedback, functionlaye
```python
from functionlayer import ContactChannel, FunctionLayer, SlackContactChannel
fl = FunctionLayer()
hl = HumanLayer()
tools = [
fl.human_as_tool(
@@ -296,7 +296,7 @@ this builds a function that can be passed to `langchain.tools.StructuredTool.fro
contact_human_in_a_dm_with_the_head_of_sales(msg: str) -> str
```
See [Langchain Human as Tool Example](../examples/langchain_human_as_tool.py) for an example of how to use this in a Langchain context.
See [Langchain Human as Tool Example](../examples/langchain/03-human_as_tool.py) for an example of how to use this in a Langchain context.
### Functionlayer Cloud
@@ -312,7 +312,7 @@ then initialize FunctionLayer with `ApprovalMethod.CLOUD`
```python
from functionlayer import ApprovalMethod, FunctionLayer
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
```
See [Getting Started](./getting-started.md) for more information on how to set up and use FunctionLayer Cloud.
@@ -333,14 +333,14 @@ to run in CLI mode, initialize FunctionLayer with `ApprovalMethod.CLI`
from functionlayer import FunctionLayer, ApprovalMethod
fl = FunctionLayer(approval_method=ApprovalMethod.CLI)
hl = HumanLayer(approval_method=ApprovalMethod.CLI)
```
Then, you can decorate your functions as normal.
```python
@fl.require_approval()
@hl.require_approval()
def send_email_to_customer(email: str, subject: str, body: str) -> str:
"""
send an email to the customer

View File

@@ -1,2 +1,2 @@
venv/
.venv/
.venv/

View File

@@ -11,4 +11,4 @@ RUN pip install \
COPY . /app
ENTRYPOINT ["python3"]
CMD ["controlflow_math.py"]
CMD ["controlflow_math.py"]

View File

@@ -11,7 +11,6 @@ Activate a new virtualenv however you prefer
pip install -r requirements.txt
```
```
python controlflow_math.py
```

View File

@@ -1,12 +1,12 @@
from dotenv import load_dotenv
from functionlayer import ApprovalMethod, FunctionLayer
from functionlayer import ApprovalMethod, HumanLayer
load_dotenv()
import controlflow as cf
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
def add(x: int, y: int) -> int:
@@ -15,7 +15,7 @@ def add(x: int, y: int) -> int:
# multiply must be approved via email by a user in the admin group
@fl.require_approval()
@hl.require_approval()
def multiply(x: int, y: int) -> int:
"""multiply two numbers"""
return x * y

View File

@@ -1,9 +1,8 @@
version: '3.7'
version: "3.7"
services:
examples:
build:
context: .
dockerfile: Dockerfile
context: .
dockerfile: Dockerfile
volumes:
- ./:/app

View File

@@ -1,2 +1,2 @@
venv/
.venv/
.venv/

View File

@@ -1 +1 @@
emails.md
emails.md

View File

@@ -12,4 +12,4 @@ RUN pip install \
COPY . /app
ENTRYPOINT ["python3"]
CMD ["crewai_math.py"]
CMD ["crewai_math.py"]

View File

@@ -13,7 +13,6 @@ Activate a new virtualenv however you prefer
pip install -r requirements.txt
```
```
python crewai_math.py
```

View File

@@ -1,13 +1,13 @@
from crewai import Agent, Crew, Task
from crewai_tools import tool
from dotenv import load_dotenv
from functionlayer import ApprovalMethod, HumanLayer
load_dotenv()
from functionlayer import ApprovalMethod, FunctionLayer
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
PROMPT = """multiply 2 and 5, then add 32 to the result"""

View File

@@ -1,40 +1,37 @@
from crewai import Agent, Crew, Task
from crewai_tools import tool
from dotenv import load_dotenv
load_dotenv()
from functionlayer import ApprovalMethod, FunctionLayer
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
task_prompt = """
Your task is to check the list of recently signed up customers, get info about their onboarding progress,
and send each one an email to encourage them to complete the onboarding process. You should
and send each one an email to encourage them to complete the onboarding process. You should
offer a meeting to help them where it makes sense.
If they are fully onboard, you should simply request feedback and offer a meeting.
"""
@tool
def get_recently_signed_up_customers() -> list[str]:
"""get a list of customers that signed up recently"""
return [
"danny@metacorp.com",
"terri@acmestuff.com"
]
return ["danny@metacorp.com", "terri@acmestuff.com"]
@tool
def get_info_about_customer(customer_email: str) -> str:
"""get info about a customer"""
if customer_email == "danny@metacorp.com":
return """
This customer has completed most of the onboarding steps,
but still needs to invite a few team members before they can be
but still needs to invite a few team members before they can be
considered fully onboarded
"""
else:
@@ -47,23 +44,25 @@ def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to a user"""
# write to a local file so we can inspect after
with open ("emails.md", "a") as f:
f.write(f"""{subject}
with open("emails.md", "a") as f:
f.write(
f"""{subject}
=============
To: {to}
* * *
* * *
{body}
* * *\n\n\n\n""")
* * *\n\n\n\n"""
)
return f"Email sent to {to} with subject: {subject}"
general_agent = Agent(
role="Onboarding Agent",
goal="""Ensure all customers of the SaaS product, SuperCI, are onboarded successfully and
goal="""Ensure all customers of the SaaS product, SuperCI, are onboarded successfully and
getting value from all features.""",
backstory="""
You are a seasoned customer success agent. You check on the progress customers
@@ -85,9 +84,11 @@ task = Task(
crew = Crew(agents=[general_agent], tasks=[task], verbose=2)
def main():
return crew.kickoff()
if __name__ == "__main__":
result = main()
print("\n\n---------- RESULT ----------\n\n")

View File

@@ -1,20 +1,18 @@
from channels import dm_with_head_of_marketing
from crewai import Agent, Crew, Task
from crewai_tools import tool
from dotenv import load_dotenv
from channels import dm_with_head_of_marketing
load_dotenv()
from functionlayer import ApprovalMethod, FunctionLayer
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
task_prompt = """
Your task is to check the list of recently signed up customers, get info about their onboarding progress,
and send each one an email to encourage them to complete the onboarding process. You should
and send each one an email to encourage them to complete the onboarding process. You should
offer a meeting to help them where it makes sense.
If they are fully onboard, you should simply request feedback and offer a meeting.
@@ -23,22 +21,20 @@ Get approval from the head of marketing before sending
"""
@tool
def get_recently_signed_up_customers() -> list[str]:
"""get a list of customers that signed up recently"""
return [
"danny@metacorp.com",
"terri@acmestuff.com"
]
return ["danny@metacorp.com", "terri@acmestuff.com"]
@tool
def get_info_about_customer(customer_email: str) -> str:
"""get info about a customer"""
if customer_email == "danny@metacorp.com":
return """
This customer has completed most of the onboarding steps,
but still needs to invite a few team members before they can be
but still needs to invite a few team members before they can be
considered fully onboarded
"""
else:
@@ -51,16 +47,18 @@ def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to a user"""
# write to a local file so we can inspect after
with open ("emails.md", "a") as f:
f.write(f"""{subject}
with open("emails.md", "a") as f:
f.write(
f"""{subject}
=============
To: {to}
* * *
* * *
{body}
* * *
""")
* * *
"""
)
return f"Email sent to {to} with subject: {subject}"
@@ -93,9 +91,11 @@ task = Task(
crew = Crew(agents=[general_agent], tasks=[task], verbose=2)
def main():
return crew.kickoff()
if __name__ == "__main__":
result = main()
print("\n\n---------- RESULT ----------\n\n")

View File

@@ -1,9 +1,8 @@
version: '3.7'
version: "3.7"
services:
examples:
build:
context: .
dockerfile: Dockerfile
context: .
dockerfile: Dockerfile
volumes:
- ./:/app

View File

@@ -1,2 +1,2 @@
venv/
.venv/
.venv/

View File

@@ -1 +1 @@
emails.md
emails.md

View File

@@ -13,7 +13,7 @@ from functionlayer import FunctionLayer
load_dotenv()
fl = FunctionLayer()
hl = HumanLayer()
@tool

View File

@@ -8,11 +8,11 @@ from langchain.agents import AgentType, initialize_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from functionlayer.core.approval import ApprovalMethod, FunctionLayer
from functionlayer.core.approval import ApprovalMethod, HumanLayer
load_dotenv()
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
@tool
@@ -39,10 +39,12 @@ agent = initialize_agent(
handle_parsing_errors=True,
)
def main():
result = agent.run("multiply 2 and 5, then add 32 to the result")
print("\n\n----------Result----------\n\n")
print(result)
if __name__ == "__main__":
main()

View File

@@ -8,11 +8,11 @@ from langchain.agents import AgentType, initialize_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from functionlayer import ApprovalMethod, FunctionLayer
from functionlayer import ApprovalMethod, HumanLayer
load_dotenv()
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
@tool

View File

@@ -3,11 +3,16 @@ from dotenv import load_dotenv
from langchain.agents import AgentType, initialize_agent
from langchain_openai import ChatOpenAI
from functionlayer import ApprovalMethod, ContactChannel, FunctionLayer, SlackContactChannel
from functionlayer import (
ApprovalMethod,
ContactChannel,
HumanLayer,
SlackContactChannel,
)
load_dotenv()
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
task_prompt = """
figure out the wizard's favorite color,

View File

@@ -3,19 +3,20 @@ from dotenv import load_dotenv
from langchain.agents import AgentType, initialize_agent
from langchain_openai import ChatOpenAI
from examples.langchain.channels import (
dm_with_summer_intern,
dm_with_head_of_marketing,
dm_with_ceo,
)
from functionlayer.core.approval import (
ApprovalMethod,
FunctionLayer,
)
from .channels import (
dm_with_ceo,
dm_with_head_of_marketing,
dm_with_summer_intern,
)
load_dotenv()
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
task_prompt = """
@@ -37,7 +38,7 @@ def get_info_about_customer(customer_email: str) -> str:
"""get info about a customer"""
return """
This customer has completed most of the onboarding steps,
but still needs to invite a few team members before they can be
but still needs to invite a few team members before they can be
considered fully onboarded
"""

View File

@@ -3,19 +3,20 @@ from dotenv import load_dotenv
from langchain.agents import AgentType, initialize_agent
from langchain_openai import ChatOpenAI
from examples.langchain.channels import (
dm_with_ceo,
dm_with_head_of_marketing,
dm_with_summer_intern,
)
from functionlayer.core.approval import (
from functionlayer import (
ApprovalMethod,
FunctionLayer,
)
from .channels import (
dm_with_ceo,
dm_with_head_of_marketing,
dm_with_summer_intern,
)
load_dotenv()
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
task_prompt = """

View File

@@ -17,7 +17,7 @@ load_dotenv()
override_bot_token = os.getenv("SLACK_BOT_TOKEN")
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
task_prompt = """

View File

@@ -12,4 +12,4 @@ RUN pip install \
COPY . /app
ENTRYPOINT ["python3"]
CMD ["01-math_example.py"]
CMD ["01-math_example.py"]

View File

@@ -32,4 +32,4 @@ docker compose run examples 01-math_example.py
- [03-human_as_tool.py](03-human_as_tool.py) - A tou example of using functionlayer's `human_as_tool()` function to create a tool that the angent can use to contact a human for input
- [04-human_as_tool_onboarding.py](04-human_as_tool_onboarding.py) - A more in-depth example of an agent that uses a human_as_tool channel to get feedback on emails that are to be sent to new customers
- [05-approvals_and_humans_composite.py](05-approvals_and_humans_composite.py) - Wrapping a "contact a human call" with an approval step routed to another human
- [06-custom_bot_token.py](06-custom_bot_token.py) - Example of how to override the default slack bot token for specific channels
- [06-custom_bot_token.py](06-custom_bot_token.py) - Example of how to override the default slack bot token for specific channels

View File

@@ -1,9 +1,8 @@
version: '3.7'
version: "3.7"
services:
examples:
build:
context: .
dockerfile: Dockerfile
context: .
dockerfile: Dockerfile
volumes:
- ./:/app

View File

@@ -1,2 +1,2 @@
venv/
.venv/
.venv/

View File

@@ -11,4 +11,4 @@ RUN pip install \
COPY . /app
ENTRYPOINT ["python3"]
CMD ["math_example.py"]
CMD ["math_example.py"]

View File

@@ -2,7 +2,7 @@
This is a straightforward and simple (albeit verbose) example of tool calling with a plain OpenAI Client -
no frameworks are used to manage the tool calling loop. Among other things, this example serves as a good
"look under the hood" for how frameworks generally implement tool calling.
"look under the hood" for how frameworks generally implement tool calling.
In this example, as in others, the `multiply(x: int, y: int) -> int` function is wrapped with
`@fl.require_approval`.
@@ -39,4 +39,3 @@ INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1
INFO:__main__:last message led to 1 tool calls: [('multiply', '{"x":2,"y":5}')]
INFO:__main__:CALL tool multiply with {'x': 2, 'y': 5}
```

View File

@@ -1,9 +1,8 @@
version: '3.7'
version: "3.7"
services:
examples:
build:
context: .
dockerfile: Dockerfile
context: .
dockerfile: Dockerfile
volumes:
- ./:/app

View File

@@ -8,7 +8,7 @@ from functionlayer import ApprovalMethod, FunctionLayer
load_dotenv()
fl = FunctionLayer(approval_method=ApprovalMethod.CLOUD)
hl = HumanLayer(approval_method=ApprovalMethod.CLOUD)
PROMPT = "multiply 2 and 5, then add 32 to the result"
@@ -79,11 +79,16 @@ def run_chain(prompt: str, tools_openai: list[dict], tools_map: dict) -> str:
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
if tool_calls:
messages.append(response_message) # extend conversation with assistant's reply
messages.append(
response_message
) # extend conversation with assistant's reply
logger.info(
"last message led to %s tool calls: %s",
len(tool_calls),
[(tool_call.function.name, tool_call.function.arguments) for tool_call in tool_calls],
[
(tool_call.function.name, tool_call.function.arguments)
for tool_call in tool_calls
],
)
for tool_call in tool_calls:
function_name = tool_call.function.name
@@ -102,7 +107,11 @@ def run_chain(prompt: str, tools_openai: list[dict], tools_map: dict) -> str:
}
)
logger.info("tool %s responded with %s", function_name, function_response_json[:200])
logger.info(
"tool %s responded with %s",
function_name,
function_response_json[:200],
)
messages.append(
{
"tool_call_id": tool_call.id,

View File

@@ -6,7 +6,7 @@ from dotenv import load_dotenv
load_dotenv(dotenv_path=os.path.join(os.getcwd(), ".env"))
@click.group(name="functionlayer")
@click.group(name="humanlayer")
def cli() -> None:
pass

View File

@@ -12,7 +12,7 @@ import requests
import socketio # type: ignore
from pydantic import BaseModel
from functionlayer.core.types import (
from humanlayer.core.types import (
ContactChannel,
FunctionCall,
FunctionCallSpec,
@@ -27,11 +27,11 @@ R = TypeVar("R")
logger = logging.getLogger(__name__)
class FunctionLayerError(Exception):
class HumanLayerError(Exception):
pass
class UserDeniedError(FunctionLayerError):
class UserDeniedError(HumanLayerError):
pass
@@ -44,7 +44,7 @@ class ApprovalMethod(Enum):
CLOUD = "cloud"
class FunctionLayerWrapper:
class HumanLayerWrapper:
def __init__(self, decorator: Callable) -> None:
self.decorator = decorator
@@ -55,8 +55,8 @@ class FunctionLayerWrapper:
return self.decorator(fn)
class FunctionLayer(BaseModel):
"""🧱 FunctionLayer"""
class HumanLayer(BaseModel):
"""🧱 HumanLayer"""
model_config = {"arbitrary_types_allowed": True}
@@ -64,9 +64,7 @@ class FunctionLayer(BaseModel):
api_base_url: str | None = None
ws_base_url: str | None = None
run_id: str | None = None
approval_method: ApprovalMethod = os.getenv(
"FUNCTIONLAYER_APPROVAL_METHOD", ApprovalMethod.CLI
)
approval_method: ApprovalMethod | None
sio: socketio.AsyncClient = socketio.AsyncClient()
CLI: ApprovalMethod = ApprovalMethod.CLI
@@ -80,7 +78,7 @@ class FunctionLayer(BaseModel):
)
self.ws_base_url = self.ws_base_url or os.getenv("FUNCTIONLAYER_WS_BASE")
self.approval_method = self.approval_method or os.getenv(
"FUNCTIONLAYER_APPROVAL_METHOD"
"FUNCTIONLAYER_APPROVAL_METHOD", ApprovalMethod.CLI
)
self.run_id = self.run_id or os.getenv("FUNCTIONLAYER_RUN_ID", genid("run"))
if not self.api_key and self.approval_method is not ApprovalMethod.CLI:
@@ -88,11 +86,11 @@ class FunctionLayer(BaseModel):
raise ValueError(exception)
def __str__(self):
return "FunctionLayer()"
return "HumanLayer()"
def require_approval(
self, contact_channel: ContactChannel | None = None
) -> FunctionLayerWrapper:
) -> HumanLayerWrapper:
def decorator(fn):
if self.approval_method is ApprovalMethod.CLI:
return self._approve_cli(fn)
@@ -102,7 +100,7 @@ class FunctionLayer(BaseModel):
exception = f"Approval method {self.approval_method} not implemented"
raise NotImplementedError(exception)
return FunctionLayerWrapper(decorator)
return HumanLayerWrapper(decorator)
def _approve_cli(self, fn: Callable[[T], R]) -> Callable[[T], R]:
@wraps(fn)

View File

@@ -27,7 +27,7 @@ class SlackContactChannel(BaseModel):
# a bot token to override the default contact channel
# if you use a custom bot token, you must set your app's
# slack webhook destination appropriately so your functionlayer server can receive them
# slack webhook destination appropriately so your humanlayer server can receive them
bot_token: str | None = None
#
# bot_token_ref: str | None

View File

@@ -1,12 +1,12 @@
[tool.poetry]
name = "functionlayer-ai"
name = "humanlayer"
version = "0.3.0"
description = "functionlayer-ai"
authors = ["functionlayer authors <dexter@metalytics.dev>"]
repository = "https://github.com/functionlayer/functionlayer"
description = "humanlayer"
authors = ["humanlayer authors <dexter@metalytics.dev>"]
repository = "https://github.com/humanlayer/humanlayer"
readme = "README.md"
packages = [
{include = "functionlayer"}
{include = "humanlayer"}
]
[tool.poetry.dependencies]
@@ -18,8 +18,8 @@ aiohttp = "3.10.2"
requests = "2.32.3"
[tool.poetry.scripts]
fl = "functionlayer.cli.main:cli"
functionlayer = "functionlayer.cli.main:cli"
hl = "humanlayer.cli.main:cli"
humanlayer = "humanlayer.cli.main:cli"
[tool.poetry.group.dev.dependencies]
pytest = "^7.2.0"
@@ -40,7 +40,7 @@ requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.mypy]
files = ["functionlayer"]
files = ["humanlayer"]
disallow_untyped_defs = "True"
disallow_any_unimported = "True"
no_implicit_optional = "True"
@@ -109,13 +109,13 @@ skip_empty = true
[tool.coverage.run]
branch = true
source = ["functionlayer"]
source = ["humanlayer"]
[tool.ruff.per-file-ignores]
"tests/*" = ["S101"]
"functionlayer/cli/main.py" = ["TRY003"]
"functionlayer/core/types.py" = ["TRY003"]
"humanlayer/cli/main.py" = ["TRY003"]
"humanlayer/core/types.py" = ["TRY003"]
[tool.ruff.mccabe]
max-complexity = 12