Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/app/agents/page.tsx
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly as with the competitors research PR, I think that docs updates should be on separate PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you think this should be a separate PR?
We got a new feature and its docs i believe it would be better to keep them as a one whole commit, but please let me know

Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default async function Page() {
<AgentCard name="Catalog Enrichment" imageSrc="/tools/enthusiast/img/agents/catalog-enrichment.png" href="/agents/catalog-enrichment" />
<AgentCard name="Purchase Order OCR" imageSrc="/tools/enthusiast/img/agents/purchase-order-ocr.png" href="/agents/purchase-order-ocr" />
<AgentCard name="User Manual Search" imageSrc="/tools/enthusiast/img/agents/user-manual-search.png" href="/agents/user-manual-search" />
<AgentCard name="Invoice Scanning" href="/agents/invoice-scanning" />
</Cards>
<H2>Build a Custom Agent</H2>
<P>These integrations are just the beginning, and we know that you may need more specialized functionality beyond what we've included here. Enthusiast gives you the flexibility to build entirely custom agents from the ground up, tailored to your needs.</P>
Expand Down
23 changes: 23 additions & 0 deletions docs/content/agents/invoice-scanning.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Agent from "@/components/agent";

export const description = "The Invoice Scanning agent extracts attributes from invoices. It can be customized to pull different attribute sets and can flag unclear or missing information, triggering a human-in-the-loop step when clarification is required.";

export const useCases = [
{
title: "TO DO",
description: "TO DO"
},
{
title: "TO DO",
description: "TO DO"
},
];

<Agent
name="Invoice Scanning"
integrationKey="enthusiast-agent-invoice-scanning"
pipName="enthusiast-agent-invoice-scanning"
registerAgentModule="enthusiast_agent_invoice_scanning"
agentDescription={description}
agentUseCases={useCases}
/>
18 changes: 18 additions & 0 deletions plugins/enthusiast-agent-invoice-scanning/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Enthusiast Invoice Scanning Agent

The Invoice Scanning agent extracts attributes from invoices. It can be customized to pull different attribute sets and can flag unclear or missing information, triggering a human-in-the-loop step when clarification is required.

## Installing the Invoice scanning Agent

Run the following command inside your application directory:
```commandline
poetry add enthusiast-agent-invoice-scanning
```

Then, register the agent in your config/settings_override.py.

```python
AVAILABLE_AGENTS = [
"enthusiast_agent_invoice_scanning.InvoiceScanningAgent"
]
```
1,218 changes: 1,218 additions & 0 deletions plugins/enthusiast-agent-invoice-scanning/poetry.lock

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions plugins/enthusiast-agent-invoice-scanning/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[project]
name = "enthusiast-agent-invoice-scanning"
version = "1.0.0"
description = "Example implementation of a invoice scanning agent for Enthusiast"
authors = [
{name = "Damian Sowiński",email = "damian.sowinski@upsidelab.io"}
]
readme = "README.md"
requires-python = ">=3.10,<4"
dependencies = [
"enthusiast-common (>=1.5.0,<2.0.0)",
"langchain (>=0.3.26,<0.4.0)",
"enthusiast_agent_re_act (>=1.3.0)",
"enthusiast_agent_tool_calling (>=1.0.0)",
]

[tool.poetry]
packages = [{include = "enthusiast_agent_invoice_scanning", from = "src"}]


[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .agent import InvoiceScanningAgent

__all__ = ["InvoiceScanningAgent"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from enthusiast_agent_tool_calling import BaseToolCallingAgent
from enthusiast_common.utils import RequiredFieldsModel
from pydantic import Field, Json


class InvoiceScanningAgentPromptInput(RequiredFieldsModel):
output_format: Json = Field(
title="Output format",
description="Output format of the extracted data",
default='{"invoice_number": "string", "issued_at": "string", "supplier_name": "string", "gross_amount": "number"}',
)


class InvoiceScanningAgent(BaseToolCallingAgent):
AGENT_KEY = "enthusiast-agent-invoice-scanning"
NAME = "Invoice Scanning"
PROMPT_INPUT = InvoiceScanningAgentPromptInput
FILE_UPLOAD = True

def get_answer(self, input_text: str) -> str:
agent_executor = self._build_agent_executor()
agent_output = agent_executor.invoke(
{"input": input_text, "data_format": self.PROMPT_INPUT.output_format}, config=self._build_invoke_config()
)
return agent_output["output"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from enthusiast_common.config import AgentConfigWithDefaults
from enthusiast_common.config.prompts import ChatPromptTemplateConfig, Message, MessageRole

from .agent import InvoiceScanningAgent
from .prompt import INVOICE_SCANNING_TOOL_CALLING_AGENT_PROMPT


def get_config() -> AgentConfigWithDefaults:
return AgentConfigWithDefaults(
prompt_template=ChatPromptTemplateConfig(
messages=[
Message(
role=MessageRole.SYSTEM,
content=INVOICE_SCANNING_TOOL_CALLING_AGENT_PROMPT,
),
Message(role=MessageRole.PLACEHOLDER, content="{chat_history}"),
Message(role=MessageRole.USER, content="{input}"),
Message(role=MessageRole.PLACEHOLDER, content="{agent_scratchpad}"),
]
),
agent_class=InvoiceScanningAgent,
tools=InvoiceScanningAgent.TOOLS,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
INVOICE_SCANNING_TOOL_CALLING_AGENT_PROMPT = """
I want you to help extracting and describing in details data from invoices.
In case of any missing information carefully collect it one by one.
In tools specify exactly what are you looking for.
You need to return result data in given shape: {data_format}.
Always verify your answer
Rules:
- Return only json
- Numbers must be plain numbers (no quotes).
- Booleans must be true/false (no quotes).
- Nulls must be null (no quotes).
- No additional explanation
- If key does not apply return null
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .agent import InvoiceScanningAgent

__all__ = ["InvoiceScanningAgent"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from enthusiast_agent_re_act import BaseReActAgent, StructuredReActOutputParser
from enthusiast_common.utils import RequiredFieldsModel
from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.tools import render_text_description_and_args
from pydantic import Field, Json


class InvoiceScanningAgentPromptInput(RequiredFieldsModel):
output_format: Json = Field(
title="Output format",
description="Output format of the extracted data",
default='{"invoice_number": "string", "issued_at": "string", "supplier_name": "string", "gross_amount": "number"}',
)


class InvoiceScanningAgent(BaseReActAgent):
AGENT_KEY = "enthusiast-agent-invoice-scanning"
NAME = "Invoice Scanning"
PROMPT_INPUT = InvoiceScanningAgentPromptInput
FILE_UPLOAD = True

def _build_agent_executor(self) -> AgentExecutor:
tools = self._build_tools()
agent = create_react_agent(
tools=tools,
llm=self._llm,
prompt=self._prompt,
tools_renderer=render_text_description_and_args,
output_parser=StructuredReActOutputParser(),
)
return AgentExecutor(
agent=agent,
tools=tools,
memory=self._build_memory(),
verbose=True,
return_intermediate_steps=True,
handle_parsing_errors=True,
)

def get_answer(self, input_text: str) -> str:
agent_executor = self._build_agent_executor()
response = agent_executor.invoke(
{"input": input_text, "data_format": self.PROMPT_INPUT.output_format}, config=self._build_invoke_config()
)
return response["output"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from enthusiast_common.config import AgentConfigWithDefaults
from enthusiast_common.config.prompts import PromptTemplateConfig

from .agent import InvoiceScanningAgent
from .prompt import INVOICE_SCANNING_RE_ACT_AGENT_PROMPT


def get_config() -> AgentConfigWithDefaults:
return AgentConfigWithDefaults(
prompt_template=PromptTemplateConfig(
input_variables=["tools", "tool_names", "input", "agent_scratchpad", "data_format"],
prompt_template=INVOICE_SCANNING_RE_ACT_AGENT_PROMPT,
),
agent_class=InvoiceScanningAgent,
tools=InvoiceScanningAgent.TOOLS,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
INVOICE_SCANNING_RE_ACT_AGENT_PROMPT = """
I want you to help extracting and describing in details invoice data using the ReACT (Reasoning and Acting) approach.
In case of any missing information carefully collect it one by one.
In tools specify exactly what are you looking for.
You need to return final answer in given shape: {data_format}
Rules:
- Return only json
- Numbers must be plain numbers (no quotes).
- Booleans must be true/false (no quotes).
- Nulls must be null (no quotes).
- No additional explanation
- If key does not apply return null

Always verify your answer
Always return output in following format: <Final Answer: <output>>
Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).

Valid "action" values: {tool_names}

Provide only ONE action per $JSON_BLOB, as shown:

```
{{
"action": $TOOL_NAME,
"action_input": $INPUT
}}
```
For each step, follow the format:
User query: the user's question or request
Thought: what you should do next
Action:
{{
"action": "<tool>",
"action_input": <tool_input>
}}
Observation: the result returned by the tool
... (repeat Thought/Action/Action Input/Observation as needed)
Thought: I now have the necessary information
Final Answer: the response to the user

Here are the tools you can use:
{tools}

Example 1:
User query: I want to get x,y,z.
Thought: I need to extract specified data.
Action: {{
"action": the tool to use, one of [{tool_names}],
"action_input": <tool_input>
}}
Observation: Some values are missing.
Thought: I need to extract them as well.
Action:
{{
"action": the verification tool to use, one of [{tool_names}],
"action_input": <tool_input>
}}
Observation: I got a all data.
Final Answer: Extracted data is x,y,z

Example 2:
User query: I want to get x,y,z.
Thought: I need to extract specified data.
Action: {{
"action": the tool to use, one of [{tool_names}],
"action_input": <tool_input>
}}
Observation: There are multiple values very similar for user's query.
Thought: I need to ask him to specify what to do.
Final Answer: In this document we got such similar data: <describe each one>, which one you mean in Z?

Do not came up with any other types of JSON than specified above.
Your output to user should always begin with '''Final Answer: <output>'''
Begin!
Chat history: {chat_history}
User query: {input}
{agent_scratchpad}"""
Loading