hyperforge-generate 1.0.0.post24__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hyperforge_generate-1.0.0.post24/PKG-INFO +19 -0
- hyperforge_generate-1.0.0.post24/README.md +1 -0
- hyperforge_generate-1.0.0.post24/pyproject.toml +40 -0
- hyperforge_generate-1.0.0.post24/setup.cfg +4 -0
- hyperforge_generate-1.0.0.post24/src/hyperforge_generate/__init__.py +0 -0
- hyperforge_generate-1.0.0.post24/src/hyperforge_generate/agent.py +101 -0
- hyperforge_generate-1.0.0.post24/src/hyperforge_generate/config.py +26 -0
- hyperforge_generate-1.0.0.post24/src/hyperforge_generate.egg-info/PKG-INFO +19 -0
- hyperforge_generate-1.0.0.post24/src/hyperforge_generate.egg-info/SOURCES.txt +11 -0
- hyperforge_generate-1.0.0.post24/src/hyperforge_generate.egg-info/dependency_links.txt +1 -0
- hyperforge_generate-1.0.0.post24/src/hyperforge_generate.egg-info/requires.txt +1 -0
- hyperforge_generate-1.0.0.post24/src/hyperforge_generate.egg-info/top_level.txt +1 -0
- hyperforge_generate-1.0.0.post24/tests/test_generate.py +83 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyperforge_generate
|
|
3
|
+
Version: 1.0.0.post24
|
|
4
|
+
Summary: External Hyperforge agent
|
|
5
|
+
Author-email: Nuclia <nucliadb@nuclia.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://progress.com
|
|
8
|
+
Project-URL: Repository, https://github.com/nuclia/forge
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: <4,>=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: hyperforge
|
|
18
|
+
|
|
19
|
+
# Generate Hyperforge agents
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Generate Hyperforge agents
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hyperforge_generate"
|
|
7
|
+
version = "1.0.0.post24"
|
|
8
|
+
license = "Apache-2.0"
|
|
9
|
+
description = "External Hyperforge agent"
|
|
10
|
+
authors = [{ name = "Nuclia", email = "nucliadb@nuclia.com" }]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python",
|
|
14
|
+
"Programming Language :: Python :: 3.10",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.10, <4"
|
|
21
|
+
dependencies = ["hyperforge"]
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"pytest",
|
|
26
|
+
"pytest-benchmark",
|
|
27
|
+
"pytest-docker-fixtures>=1.4.2",
|
|
28
|
+
"pytest-lazy-fixtures",
|
|
29
|
+
"pytest-recording",
|
|
30
|
+
"pytest-asyncio",
|
|
31
|
+
"pytest-cov",
|
|
32
|
+
"pytest-mock",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://progress.com"
|
|
37
|
+
Repository = "https://github.com/nuclia/forge"
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
asyncio_mode = "auto"
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from time import time
|
|
2
|
+
from uuid import uuid4
|
|
3
|
+
|
|
4
|
+
from hyperforge.agent import Agent
|
|
5
|
+
from hyperforge.configure import agent
|
|
6
|
+
from hyperforge.manager import Manager
|
|
7
|
+
from hyperforge.memory import QuestionMemory
|
|
8
|
+
from hyperforge.trace import trace_agent
|
|
9
|
+
from nuclia.lib.nua_responses import ChatModel, UserPrompt
|
|
10
|
+
|
|
11
|
+
from hyperforge import PROMPT_ENVIRONMENT
|
|
12
|
+
from hyperforge_generate.config import GenerateAgentConfig
|
|
13
|
+
|
|
14
|
+
GENERATE_PROMPT = """
|
|
15
|
+
Generate the following request using **only** the provided context. Refrain from incorporating any outside knowledge. If the context is insufficient to answer the question comprehensively, respond with: "Not enough data to generate this."
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
{% if rules -%}
|
|
19
|
+
# Generation Rules
|
|
20
|
+
{% for rule in rules -%}
|
|
21
|
+
- {{rule}}
|
|
22
|
+
{% endfor -%}
|
|
23
|
+
{% endif -%}
|
|
24
|
+
|
|
25
|
+
{{prompt}}
|
|
26
|
+
|
|
27
|
+
{{context}}
|
|
28
|
+
|
|
29
|
+
MAIN QUESTION: {{question}}
|
|
30
|
+
|
|
31
|
+
# Notes
|
|
32
|
+
- Use the context provided without being overly selective.
|
|
33
|
+
- Please try to generate if possible, even if it requires to make a bit of a deduction.
|
|
34
|
+
- If they are images attached, look through them carefully.
|
|
35
|
+
- For rules related to charts or images, pay extra attention to extracting details and interpreting data presented visually. Think about it carefully before giving inaccurate interpretations
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
GENERATE_PROMPT_TEMPLATE = PROMPT_ENVIRONMENT.from_string(GENERATE_PROMPT)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@agent(
|
|
43
|
+
id="generate",
|
|
44
|
+
agent_type="generation",
|
|
45
|
+
title="Generate Answer",
|
|
46
|
+
description="Generate answers based on provided context.",
|
|
47
|
+
config_schema=GenerateAgentConfig,
|
|
48
|
+
)
|
|
49
|
+
class GenerateAgent(Agent[GenerateAgentConfig]):
|
|
50
|
+
__root_agent__ = True
|
|
51
|
+
|
|
52
|
+
@trace_agent
|
|
53
|
+
async def __call__(
|
|
54
|
+
self,
|
|
55
|
+
memory: QuestionMemory,
|
|
56
|
+
manager: Manager,
|
|
57
|
+
):
|
|
58
|
+
# For each context in memory add context query, summary and answer onto a text and the initial question
|
|
59
|
+
prompt = GENERATE_PROMPT_TEMPLATE.render(
|
|
60
|
+
question=memory.original_question,
|
|
61
|
+
context=memory.contexts_minimal(),
|
|
62
|
+
prompt=self.config.prompt,
|
|
63
|
+
rules=memory.generation_rules,
|
|
64
|
+
)
|
|
65
|
+
t0 = time()
|
|
66
|
+
|
|
67
|
+
chat_model = ChatModel(
|
|
68
|
+
user_id="generate",
|
|
69
|
+
question="",
|
|
70
|
+
user_prompt=UserPrompt(prompt=prompt),
|
|
71
|
+
format_prompt=False,
|
|
72
|
+
generative_model=self.config.model,
|
|
73
|
+
max_tokens=2000,
|
|
74
|
+
tracking=memory.get_tracking_info(),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
agent_path = f"/generation/{self.config.id if self.config.id else 'default'}"
|
|
78
|
+
resp, input_tokens, output_tokens = await manager.execute_raw(
|
|
79
|
+
chat_model, memory=memory, module="generate", agent_path=agent_path
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
generated_text = resp.answer or ""
|
|
83
|
+
|
|
84
|
+
memory.generated_texts[uuid4().hex] = generated_text
|
|
85
|
+
|
|
86
|
+
await memory.add_generated_text(
|
|
87
|
+
self.config.id if self.config.id else "default", generated_text
|
|
88
|
+
)
|
|
89
|
+
# We assume that JSON generation always produces an answer
|
|
90
|
+
memory.is_answered = True
|
|
91
|
+
|
|
92
|
+
await memory.add_step(
|
|
93
|
+
step_module=self.config.module,
|
|
94
|
+
step_title=self.step_title("Generate"),
|
|
95
|
+
step_value="",
|
|
96
|
+
step_reason="",
|
|
97
|
+
step_agent_path=agent_path,
|
|
98
|
+
timeit=time() - t0,
|
|
99
|
+
input_nuclia_tokens=input_tokens,
|
|
100
|
+
output_nuclia_tokens=output_tokens,
|
|
101
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Literal, Optional
|
|
2
|
+
|
|
3
|
+
from hyperforge.agent import AgentConfig
|
|
4
|
+
from hyperforge.utils import WidgetType
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from pydantic.config import ConfigDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GenerateAgentConfig(AgentConfig):
|
|
10
|
+
model_config = ConfigDict(title="Generate")
|
|
11
|
+
module: Literal["generate"] = "generate"
|
|
12
|
+
prompt: Optional[str] = Field(
|
|
13
|
+
None,
|
|
14
|
+
json_schema_extra={
|
|
15
|
+
"show_in_node": True,
|
|
16
|
+
"widget": WidgetType.EXPANDABLE_TEXTAREA,
|
|
17
|
+
},
|
|
18
|
+
)
|
|
19
|
+
model: str = Field(
|
|
20
|
+
default="chatgpt-azure-4o-mini",
|
|
21
|
+
title="Generative model",
|
|
22
|
+
description="Model used to generate the response",
|
|
23
|
+
json_schema_extra={"widget": WidgetType.MODEL_SELECT},
|
|
24
|
+
)
|
|
25
|
+
images: bool = False
|
|
26
|
+
generate_image: bool = False
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyperforge_generate
|
|
3
|
+
Version: 1.0.0.post24
|
|
4
|
+
Summary: External Hyperforge agent
|
|
5
|
+
Author-email: Nuclia <nucliadb@nuclia.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://progress.com
|
|
8
|
+
Project-URL: Repository, https://github.com/nuclia/forge
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: <4,>=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: hyperforge
|
|
18
|
+
|
|
19
|
+
# Generate Hyperforge agents
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/hyperforge_generate/__init__.py
|
|
4
|
+
src/hyperforge_generate/agent.py
|
|
5
|
+
src/hyperforge_generate/config.py
|
|
6
|
+
src/hyperforge_generate.egg-info/PKG-INFO
|
|
7
|
+
src/hyperforge_generate.egg-info/SOURCES.txt
|
|
8
|
+
src/hyperforge_generate.egg-info/dependency_links.txt
|
|
9
|
+
src/hyperforge_generate.egg-info/requires.txt
|
|
10
|
+
src/hyperforge_generate.egg-info/top_level.txt
|
|
11
|
+
tests/test_generate.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hyperforge
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hyperforge_generate
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from hyperforge.manager import Manager
|
|
6
|
+
from hyperforge.memory import Chunk, Context
|
|
7
|
+
from hyperforge.memory.memory import EphemeralSessionMemory
|
|
8
|
+
from hyperforge.models import MemoryConfig, Rules
|
|
9
|
+
from hyperforge_generate.agent import GenerateAgent
|
|
10
|
+
from hyperforge_generate.config import GenerateAgentConfig
|
|
11
|
+
|
|
12
|
+
pytestmark = pytest.mark.asyncio
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def test_generate_agent():
|
|
16
|
+
# 1. Create a mocked manager
|
|
17
|
+
manager = MagicMock(spec=Manager)
|
|
18
|
+
|
|
19
|
+
# Mock execute_raw response.
|
|
20
|
+
# resp must be an object with an `answer` attribute.
|
|
21
|
+
resp_mock = MagicMock()
|
|
22
|
+
resp_mock.answer = "This is a generated response based on context."
|
|
23
|
+
manager.execute_raw = AsyncMock(return_value=(resp_mock, 10, 20))
|
|
24
|
+
|
|
25
|
+
# 2. Create the configuration for GenerateAgent
|
|
26
|
+
config = GenerateAgentConfig(
|
|
27
|
+
prompt="Synthesize an answer.", model="chatgpt-azure-4o-mini"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# 3. Create the GenerateAgent instance
|
|
31
|
+
agent = GenerateAgent(config=config)
|
|
32
|
+
|
|
33
|
+
# 4. Set up EphemeralSessionMemory and QuestionMemory
|
|
34
|
+
session = EphemeralSessionMemory.from_config(
|
|
35
|
+
config=MemoryConfig(), agent_id="test", workflow_id="test", rules=Rules()
|
|
36
|
+
)
|
|
37
|
+
session.init("test-session")
|
|
38
|
+
memory = session.start_question("What is the capital of France?")
|
|
39
|
+
|
|
40
|
+
# Let's add some contexts and rules to memory to verify prompt rendering
|
|
41
|
+
context_obj = Context(
|
|
42
|
+
id="context-1",
|
|
43
|
+
original_question_uuid="question-1",
|
|
44
|
+
actual_question_uuid="question-1",
|
|
45
|
+
question="What is the capital of France?",
|
|
46
|
+
source="test-source",
|
|
47
|
+
agent="static",
|
|
48
|
+
title="Static Context",
|
|
49
|
+
chunks=[Chunk(chunk_id="chunk-1", text="The capital of France is Paris.")],
|
|
50
|
+
)
|
|
51
|
+
memory.contexts.append(context_obj)
|
|
52
|
+
|
|
53
|
+
# Add generation rules
|
|
54
|
+
memory.generation_rules = OrderedDict([("Only use the context.", "")])
|
|
55
|
+
|
|
56
|
+
# 5. Call the agent
|
|
57
|
+
await agent(memory=memory, manager=manager)
|
|
58
|
+
|
|
59
|
+
# 6. Assertions
|
|
60
|
+
# Verify execute_raw was called with expected arguments
|
|
61
|
+
assert manager.execute_raw.called
|
|
62
|
+
call_args = manager.execute_raw.call_args[0]
|
|
63
|
+
chat_model = call_args[0]
|
|
64
|
+
|
|
65
|
+
# Verify that prompt template rendered correctly containing context, question, and rules
|
|
66
|
+
assert "The capital of France is Paris." in chat_model.user_prompt.prompt
|
|
67
|
+
assert "What is the capital of France?" in chat_model.user_prompt.prompt
|
|
68
|
+
assert "Only use the context." in chat_model.user_prompt.prompt
|
|
69
|
+
assert "Synthesize an answer." in chat_model.user_prompt.prompt
|
|
70
|
+
|
|
71
|
+
# Verify the generated text was added to memory
|
|
72
|
+
assert memory.is_answered is True
|
|
73
|
+
assert len(memory.generated_texts) == 2
|
|
74
|
+
for val in memory.generated_texts.values():
|
|
75
|
+
assert val == "This is a generated response based on context."
|
|
76
|
+
|
|
77
|
+
# Verify a step was added
|
|
78
|
+
assert len(memory.steps) == 1
|
|
79
|
+
step = memory.steps[0]
|
|
80
|
+
assert step.module == "generate"
|
|
81
|
+
assert step.title == "Generate: Generate"
|
|
82
|
+
assert step.input_nuclia_tokens == 10
|
|
83
|
+
assert step.output_nuclia_tokens == 20
|