hyperforge-historical 1.0.0.post19__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.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: hyperforge_historical
3
+ Version: 1.0.0.post19
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
+ # Historical Hyperforge agents
@@ -0,0 +1 @@
1
+ # Historical Hyperforge agents
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hyperforge_historical"
7
+ version = "1.0.0.post19"
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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,47 @@
1
+ from time import time
2
+
3
+ from hyperforge.agent import Agent
4
+ from hyperforge.configure import agent
5
+ from hyperforge.manager import Manager
6
+ from hyperforge.memory import QuestionMemory
7
+ from hyperforge.trace import trace_agent
8
+
9
+ from hyperforge_historical.config import HistoricalAgentConfig
10
+
11
+
12
+ @agent(
13
+ id="historical",
14
+ agent_type="preprocess",
15
+ title="Historical Context",
16
+ description="Agent that provides historical context for a given question.",
17
+ config_schema=HistoricalAgentConfig,
18
+ )
19
+ class HistoricalAgent(Agent[HistoricalAgentConfig]):
20
+ @trace_agent
21
+ async def __call__(
22
+ self,
23
+ memory: QuestionMemory,
24
+ manager: Manager,
25
+ ):
26
+ t0 = time()
27
+ if memory.original_question is not None:
28
+ result = await memory.search_in_questions(
29
+ memory.original_question, self.config.all
30
+ )
31
+ if result.total > 0:
32
+ # Summarize the fins results
33
+
34
+ # Check if its answers
35
+
36
+ answer = ""
37
+ await memory.add_answer(
38
+ answer=answer,
39
+ module="historical",
40
+ agent_path=f"/preprocess/{self.agent_id}",
41
+ )
42
+ await memory.add_step(
43
+ step_module="historical",
44
+ step_title=self.step_title("Historical context"),
45
+ step_agent_path=f"/preprocess/{self.agent_id}",
46
+ timeit=time() - t0,
47
+ )
@@ -0,0 +1,10 @@
1
+ from typing import Literal
2
+
3
+ from hyperforge.agent import AgentConfig
4
+ from pydantic.config import ConfigDict
5
+
6
+
7
+ class HistoricalAgentConfig(AgentConfig):
8
+ model_config = ConfigDict(title="History")
9
+ all: bool
10
+ module: Literal["historical"] = "historical"
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: hyperforge_historical
3
+ Version: 1.0.0.post19
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
+ # Historical Hyperforge agents
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/hyperforge_historical/__init__.py
4
+ src/hyperforge_historical/agent.py
5
+ src/hyperforge_historical/config.py
6
+ src/hyperforge_historical.egg-info/PKG-INFO
7
+ src/hyperforge_historical.egg-info/SOURCES.txt
8
+ src/hyperforge_historical.egg-info/dependency_links.txt
9
+ src/hyperforge_historical.egg-info/requires.txt
10
+ src/hyperforge_historical.egg-info/top_level.txt
11
+ tests/test_historical.py
@@ -0,0 +1,119 @@
1
+ from unittest.mock import AsyncMock, patch
2
+
3
+ import pytest
4
+ from nucliadb_models.search import KnowledgeboxFindResults
5
+ from hyperforge.manager import Manager
6
+ from hyperforge.memory.memory import EphemeralSessionMemory
7
+ from hyperforge.models import MemoryConfig, Rules
8
+ from hyperforge_historical.agent import HistoricalAgent
9
+ from hyperforge_historical.config import HistoricalAgentConfig
10
+
11
+ def make_agent(all_sessions: bool = False) -> HistoricalAgent:
12
+ config = HistoricalAgentConfig(id="test-historical", all=all_sessions)
13
+ return HistoricalAgent(config=config)
14
+
15
+
16
+ def make_memory(question: str = "What is the capital of France?"):
17
+ session = EphemeralSessionMemory.from_config(
18
+ config=MemoryConfig(), agent_id="test", workflow_id="test", rules=Rules()
19
+ )
20
+ session.init("test-session")
21
+ return session.start_question(question)
22
+
23
+
24
+ # --- Config tests ---
25
+
26
+ def test_config_module_literal():
27
+ config = HistoricalAgentConfig(all=True)
28
+ assert config.module == "historical"
29
+
30
+
31
+ def test_step_title():
32
+ agent = make_agent()
33
+ assert agent.step_title("Historical context") == "History: Historical context"
34
+
35
+
36
+ # --- Behaviour tests ---
37
+
38
+ async def test_no_history_hit_adds_step_but_no_answer():
39
+ """When search returns no results, only a step is added, no answer."""
40
+ agent = make_agent()
41
+ memory = make_memory()
42
+
43
+ # EphemeralSessionMemory already returns total=0 by default
44
+ await agent(memory=memory, manager=None)
45
+
46
+ assert len(memory.steps) == 1
47
+ assert memory.steps[0].module == "historical"
48
+ assert len(memory.answers) == 0
49
+
50
+
51
+ async def test_history_hit_adds_empty_answer_and_step():
52
+ """When search finds results, an empty answer and a step are both recorded."""
53
+ agent = make_agent()
54
+ memory = make_memory()
55
+
56
+ with patch.object(
57
+ memory,
58
+ "search_in_questions",
59
+ new=AsyncMock(return_value=KnowledgeboxFindResults(total=2, resources={})),
60
+ ):
61
+ await agent(memory=memory, manager=None)
62
+
63
+ assert len(memory.steps) == 1
64
+ assert memory.steps[0].module == "historical"
65
+ assert len(memory.answers) == 1
66
+ # answers are stored as (text, metadata) tuples
67
+ assert memory.answers[0][0] == ""
68
+
69
+
70
+ async def test_all_false_passed_to_search():
71
+ """config.all=False is forwarded to search_in_questions."""
72
+ agent = make_agent(all_sessions=False)
73
+ memory = make_memory()
74
+
75
+ mock_search = AsyncMock(return_value=KnowledgeboxFindResults(total=0, resources={}))
76
+ with patch.object(memory, "search_in_questions", new=mock_search):
77
+ await agent(memory=memory, manager=None)
78
+
79
+ mock_search.assert_awaited_once_with(memory.original_question, False)
80
+
81
+
82
+ async def test_all_true_passed_to_search():
83
+ """config.all=True is forwarded to search_in_questions."""
84
+ agent = make_agent(all_sessions=True)
85
+ memory = make_memory()
86
+
87
+ mock_search = AsyncMock(return_value=KnowledgeboxFindResults(total=0, resources={}))
88
+ with patch.object(memory, "search_in_questions", new=mock_search):
89
+ await agent(memory=memory, manager=None)
90
+
91
+ mock_search.assert_awaited_once_with(memory.original_question, True)
92
+
93
+
94
+ async def test_step_has_correct_fields():
95
+ """Recorded step has the right module, title and agent_path."""
96
+ agent = make_agent()
97
+ memory = make_memory()
98
+
99
+ await agent(memory=memory, manager=None)
100
+
101
+ step = memory.steps[0]
102
+ assert step.module == "historical"
103
+ assert step.title == "History: Historical context"
104
+ assert step.agent_path == f"/preprocess/{agent.agent_id}"
105
+
106
+
107
+ async def test_none_question_skips_search_and_adds_step():
108
+ """When original_question is None the search is skipped but a step is still added."""
109
+ agent = make_agent()
110
+ memory = make_memory()
111
+ memory.original_question = None
112
+
113
+ mock_search = AsyncMock(return_value=KnowledgeboxFindResults(total=0, resources={}))
114
+ with patch.object(memory, "search_in_questions", new=mock_search):
115
+ await agent(memory=memory, manager=None)
116
+
117
+ mock_search.assert_not_awaited()
118
+ assert len(memory.steps) == 1
119
+ assert len(memory.answers) == 0