hyperforge-remi 1.0.0.post20__py3-none-any.whl
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_remi/__init__.py +3 -0
- hyperforge_remi/agent.py +177 -0
- hyperforge_remi/config.py +23 -0
- hyperforge_remi-1.0.0.post20.dist-info/METADATA +19 -0
- hyperforge_remi-1.0.0.post20.dist-info/RECORD +7 -0
- hyperforge_remi-1.0.0.post20.dist-info/WHEEL +5 -0
- hyperforge_remi-1.0.0.post20.dist-info/top_level.txt +1 -0
hyperforge_remi/agent.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from hyperforge.agent import Agent
|
|
4
|
+
from hyperforge.configure import agent
|
|
5
|
+
from hyperforge.manager import Manager
|
|
6
|
+
from hyperforge.memory import Chunk, Context, QuestionMemory
|
|
7
|
+
from hyperforge.trace import trace_agent
|
|
8
|
+
from nuclia_models.predict.remi import RemiResponse
|
|
9
|
+
|
|
10
|
+
from hyperforge import logger
|
|
11
|
+
from hyperforge_remi.config import (
|
|
12
|
+
ContextGranularity,
|
|
13
|
+
RemiAgentConfig,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
MAX_CONTEXTS_REMI = 60
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@agent(
|
|
20
|
+
id="remi",
|
|
21
|
+
agent_type="postprocess",
|
|
22
|
+
title="REMI Evaluation",
|
|
23
|
+
description="Agent that performs REMI evaluation.",
|
|
24
|
+
config_schema=RemiAgentConfig,
|
|
25
|
+
)
|
|
26
|
+
class RemiAgent(Agent[RemiAgentConfig]):
|
|
27
|
+
@trace_agent
|
|
28
|
+
async def __call__(
|
|
29
|
+
self,
|
|
30
|
+
memory: QuestionMemory,
|
|
31
|
+
manager: Manager,
|
|
32
|
+
):
|
|
33
|
+
error = None
|
|
34
|
+
# For each context in memory add context query, summary and answer onto a text and the initial question
|
|
35
|
+
if memory.final_answer is None:
|
|
36
|
+
raise Exception("No final answer")
|
|
37
|
+
if memory.original_question is None:
|
|
38
|
+
raise Exception("No original question")
|
|
39
|
+
|
|
40
|
+
# Only answer relevance and groundedness for now
|
|
41
|
+
# If we also did context relevance, we would combine into a single remi call
|
|
42
|
+
tasks = [
|
|
43
|
+
manager.remi(
|
|
44
|
+
question=memory.original_question,
|
|
45
|
+
answer=memory.final_answer,
|
|
46
|
+
contexts=None,
|
|
47
|
+
)
|
|
48
|
+
]
|
|
49
|
+
contexts = (
|
|
50
|
+
memory.list_contexts_minimal()
|
|
51
|
+
if self.config.context_granularity == ContextGranularity.PARTIAL_ANSWERS
|
|
52
|
+
else memory.list_chunks_markdown()
|
|
53
|
+
)
|
|
54
|
+
if len(contexts) > MAX_CONTEXTS_REMI:
|
|
55
|
+
contexts = contexts[:MAX_CONTEXTS_REMI]
|
|
56
|
+
error = f"Too many contexts for groundedness evaluation, truncated to {MAX_CONTEXTS_REMI}. "
|
|
57
|
+
|
|
58
|
+
if memory.is_answered is True:
|
|
59
|
+
tasks.append(
|
|
60
|
+
manager.remi(
|
|
61
|
+
question=None,
|
|
62
|
+
answer=memory.final_answer,
|
|
63
|
+
contexts=contexts,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
67
|
+
ev_answer_rel: RemiResponse | BaseException = results[0]
|
|
68
|
+
if memory.is_answered is True:
|
|
69
|
+
ev_groundedness: RemiResponse | BaseException | None = results[1]
|
|
70
|
+
else:
|
|
71
|
+
logger.info(
|
|
72
|
+
"Forcing REMi groundedness to 0 on question flagged as not answered"
|
|
73
|
+
)
|
|
74
|
+
ev_groundedness = RemiResponse(
|
|
75
|
+
groundedness=[0] * max(len(contexts), 1), time=0
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
response_str = ""
|
|
79
|
+
if (
|
|
80
|
+
isinstance(ev_answer_rel, BaseException)
|
|
81
|
+
or not ev_answer_rel
|
|
82
|
+
or not ev_answer_rel.answer_relevance
|
|
83
|
+
):
|
|
84
|
+
msg = "Error evaluating answer relevance. "
|
|
85
|
+
logger.warning(
|
|
86
|
+
msg + str(ev_answer_rel)
|
|
87
|
+
if isinstance(ev_answer_rel, BaseException)
|
|
88
|
+
else ""
|
|
89
|
+
)
|
|
90
|
+
error = error + msg if error else msg
|
|
91
|
+
else:
|
|
92
|
+
response_str += (
|
|
93
|
+
f"Answer relevance: {ev_answer_rel.answer_relevance.score}/5. "
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Max aggregation for groundedness, could be configurable
|
|
97
|
+
if (
|
|
98
|
+
isinstance(ev_groundedness, BaseException)
|
|
99
|
+
or not ev_groundedness
|
|
100
|
+
or not ev_groundedness.groundedness
|
|
101
|
+
):
|
|
102
|
+
msg = "Error evaluating answer groundedness. "
|
|
103
|
+
logger.warning(
|
|
104
|
+
msg + str(ev_groundedness)
|
|
105
|
+
if isinstance(ev_groundedness, BaseException)
|
|
106
|
+
else ""
|
|
107
|
+
)
|
|
108
|
+
error = error + msg if error else msg
|
|
109
|
+
else:
|
|
110
|
+
groundedness = max(
|
|
111
|
+
[g if g is not None else 0 for g in ev_groundedness.groundedness]
|
|
112
|
+
)
|
|
113
|
+
response_str += f"Answer groundedness: {groundedness}/5."
|
|
114
|
+
|
|
115
|
+
remi_chunks = []
|
|
116
|
+
|
|
117
|
+
# Use chunks directly if context granularity is chunk (markdown)
|
|
118
|
+
if self.config.context_granularity != ContextGranularity.PARTIAL_ANSWERS:
|
|
119
|
+
chunk_idx = 0
|
|
120
|
+
for context in memory.contexts:
|
|
121
|
+
agent_name = context.agent_id if context.agent_id else context.agent
|
|
122
|
+
if context.chunks:
|
|
123
|
+
for chunk in context.chunks:
|
|
124
|
+
if chunk_idx >= min(
|
|
125
|
+
MAX_CONTEXTS_REMI, len(ev_groundedness.groundedness)
|
|
126
|
+
):
|
|
127
|
+
break
|
|
128
|
+
score = ev_groundedness.groundedness[chunk_idx]
|
|
129
|
+
g_score = score if score is not None else 0
|
|
130
|
+
|
|
131
|
+
c = Chunk(
|
|
132
|
+
chunk_id=chunk.chunk_id or f"remi_chunk_{chunk_idx}",
|
|
133
|
+
title=f"Groundedness {g_score}/5 - [{agent_name}] {chunk.title or 'Untitled'}",
|
|
134
|
+
text=f"**Groundedness: {g_score}/5**\n\n{chunk.text}",
|
|
135
|
+
origin_agent=self.config.module,
|
|
136
|
+
)
|
|
137
|
+
remi_chunks.append(c)
|
|
138
|
+
chunk_idx += 1
|
|
139
|
+
else:
|
|
140
|
+
for i, context in enumerate(memory.contexts[:MAX_CONTEXTS_REMI]):
|
|
141
|
+
if i < len(ev_groundedness.groundedness):
|
|
142
|
+
score = ev_groundedness.groundedness[i]
|
|
143
|
+
g_score = score if score is not None else 0
|
|
144
|
+
agent_name = (
|
|
145
|
+
context.agent_id if context.agent_id else context.agent
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
evaluated_text = (
|
|
149
|
+
context.answer_summary_markdown()
|
|
150
|
+
if context.summary.strip()
|
|
151
|
+
else context.context_markdown()
|
|
152
|
+
)
|
|
153
|
+
c = Chunk(
|
|
154
|
+
chunk_id=context.id or f"remi_{i}",
|
|
155
|
+
title=f"Groundedness {g_score}/5 - [{agent_name}] {context.title or 'Untitled'}",
|
|
156
|
+
text=f"**Groundedness: {g_score}/5**\n\n{evaluated_text}",
|
|
157
|
+
origin_agent=self.config.module,
|
|
158
|
+
)
|
|
159
|
+
remi_chunks.append(c)
|
|
160
|
+
|
|
161
|
+
if remi_chunks:
|
|
162
|
+
remi_context = Context(
|
|
163
|
+
original_question_uuid=memory.original_question_uuid,
|
|
164
|
+
actual_question_uuid=memory.actual_question_uuid,
|
|
165
|
+
agent="remi",
|
|
166
|
+
agent_id="remi",
|
|
167
|
+
title="REMi Evaluation Breakdown",
|
|
168
|
+
summary=response_str.strip()
|
|
169
|
+
if response_str.strip()
|
|
170
|
+
else f"Evaluated {len(remi_chunks)} contexts for groundedness.",
|
|
171
|
+
question=memory.original_question or "",
|
|
172
|
+
source="remi",
|
|
173
|
+
chunks=remi_chunks,
|
|
174
|
+
)
|
|
175
|
+
if error:
|
|
176
|
+
remi_context.summary += f"\nErrors: {error}"
|
|
177
|
+
await memory.save_context("postprocess", remi_context)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from hyperforge.agent import AgentConfig
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from pydantic.config import ConfigDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ContextGranularity(str, Enum):
|
|
10
|
+
FULL = "full"
|
|
11
|
+
PARTIAL_ANSWERS = "partial_answers"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RemiAgentConfig(AgentConfig):
|
|
15
|
+
model_config = ConfigDict(title="REMi evaluation")
|
|
16
|
+
module: Literal["remi"] = "remi"
|
|
17
|
+
context_granularity: ContextGranularity = Field(
|
|
18
|
+
default=ContextGranularity.FULL,
|
|
19
|
+
title="Granularity of the contexts pieces",
|
|
20
|
+
description="Granularity of the context pieces sent to REMi for groundedness evaluation. "
|
|
21
|
+
"If 'partial_answers', the evaluation will use agent-level answer attempts to the question when available for a speedier analysis. "
|
|
22
|
+
"If 'full', the evaluation will use all individual text chunks that each agent generated for a more detailed analysis.",
|
|
23
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyperforge_remi
|
|
3
|
+
Version: 1.0.0.post20
|
|
4
|
+
Summary: Remi 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
|
+
# Remi Hyperforge agents
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
hyperforge_remi/__init__.py,sha256=eLmHV2JTI1cj_H9Xw7SAK3GnsSLfbIyUzCnbO13lO4E,54
|
|
2
|
+
hyperforge_remi/agent.py,sha256=eIg6y9Sci8dnL6aPZiZp4UmOkNNmkwvv2NleYOlvOAA,7175
|
|
3
|
+
hyperforge_remi/config.py,sha256=sWarPV5_SzGhpVrTBo46XS5TdVq3cbH2h4u9bdMvgLE,905
|
|
4
|
+
hyperforge_remi-1.0.0.post20.dist-info/METADATA,sha256=eeBfA1unaJyE0Lv9bRbC03A-cAbR21G_F8dbg_5dRT0,716
|
|
5
|
+
hyperforge_remi-1.0.0.post20.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
hyperforge_remi-1.0.0.post20.dist-info/top_level.txt,sha256=-SZGKrIg_uD9_QQD_eZwFrnvu2wb26cAEKbC1mIH07o,16
|
|
7
|
+
hyperforge_remi-1.0.0.post20.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hyperforge_remi
|