hyperforge 1.0.0.post19__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/__init__.py +16 -0
- hyperforge/agent.py +81 -0
- hyperforge/api/__init__.py +20 -0
- hyperforge/api/app.py +155 -0
- hyperforge/api/authentication.py +271 -0
- hyperforge/api/commands.py +33 -0
- hyperforge/api/internal/__init__.py +4 -0
- hyperforge/api/internal/inspect.py +30 -0
- hyperforge/api/internal/router.py +3 -0
- hyperforge/api/logging.py +18 -0
- hyperforge/api/models.py +129 -0
- hyperforge/api/session.py +197 -0
- hyperforge/api/settings.py +38 -0
- hyperforge/api/utils.py +354 -0
- hyperforge/api/v1/__init__.py +23 -0
- hyperforge/api/v1/agents.py +531 -0
- hyperforge/api/v1/interaction.py +430 -0
- hyperforge/api/v1/mcp_content.py +311 -0
- hyperforge/api/v1/mcp_interaction.py +322 -0
- hyperforge/api/v1/oauth.py +60 -0
- hyperforge/api/v1/prompt.py +129 -0
- hyperforge/api/v1/router.py +3 -0
- hyperforge/api/v1/schema.py +56 -0
- hyperforge/api/v1/session.py +182 -0
- hyperforge/api/v1/utils.py +12 -0
- hyperforge/api/v1/workflows.py +643 -0
- hyperforge/arag.py +28 -0
- hyperforge/broker/__init__.py +52 -0
- hyperforge/broker/local.py +116 -0
- hyperforge/broker/redis.py +161 -0
- hyperforge/configure.py +571 -0
- hyperforge/context/__init__.py +0 -0
- hyperforge/context/agent.py +377 -0
- hyperforge/context/config.py +103 -0
- hyperforge/database.py +3 -0
- hyperforge/db/__init__.py +6 -0
- hyperforge/db/agents.py +1521 -0
- hyperforge/db/encryption.py +91 -0
- hyperforge/db/exceptions.py +26 -0
- hyperforge/db/settings.py +16 -0
- hyperforge/db/workflow_cleanup.py +69 -0
- hyperforge/definition.py +13 -0
- hyperforge/driver.py +31 -0
- hyperforge/dummy.py +28 -0
- hyperforge/engine.py +189 -0
- hyperforge/exceptions.py +14 -0
- hyperforge/feature_flag.py +105 -0
- hyperforge/fixtures.py +602 -0
- hyperforge/interaction.py +116 -0
- hyperforge/llm.py +75 -0
- hyperforge/manager.py +432 -0
- hyperforge/memory/__init__.py +5 -0
- hyperforge/memory/memory.py +974 -0
- hyperforge/minimal_fixtures.py +75 -0
- hyperforge/models.py +336 -0
- hyperforge/nua.py +336 -0
- hyperforge/openapi.py +63 -0
- hyperforge/prompts.py +188 -0
- hyperforge/pubsub.py +90 -0
- hyperforge/py.typed +0 -0
- hyperforge/redis_utils.py +82 -0
- hyperforge/retrieval/__init__.py +0 -0
- hyperforge/retrieval/agent.py +169 -0
- hyperforge/retrieval/config.py +94 -0
- hyperforge/server/__init__.py +5 -0
- hyperforge/server/cache.py +131 -0
- hyperforge/server/run.py +109 -0
- hyperforge/server/sandbox.py +60 -0
- hyperforge/server/session.py +421 -0
- hyperforge/server/settings.py +47 -0
- hyperforge/server/utils.py +57 -0
- hyperforge/server/web.py +31 -0
- hyperforge/settings.py +18 -0
- hyperforge/standalone/__init__.py +5 -0
- hyperforge/standalone/agent.py +189 -0
- hyperforge/standalone/app.py +264 -0
- hyperforge/standalone/config.py +137 -0
- hyperforge/standalone/const.py +1 -0
- hyperforge/standalone/run.py +60 -0
- hyperforge/standalone/settings.py +133 -0
- hyperforge/standalone/ui_router.py +241 -0
- hyperforge/trace.py +42 -0
- hyperforge/utils/__init__.py +112 -0
- hyperforge/utils/http.py +48 -0
- hyperforge/workflows.py +44 -0
- hyperforge-1.0.0.post19.dist-info/METADATA +95 -0
- hyperforge-1.0.0.post19.dist-info/RECORD +90 -0
- hyperforge-1.0.0.post19.dist-info/WHEEL +5 -0
- hyperforge-1.0.0.post19.dist-info/entry_points.txt +8 -0
- hyperforge-1.0.0.post19.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Lightweight pytest fixtures for hyperforge agents.
|
|
2
|
+
|
|
3
|
+
This module only depends on pytest and the stdlib — no docker, database, or
|
|
4
|
+
nucliadb deps. Safe to use as a pytest plugin in any agent repo:
|
|
5
|
+
|
|
6
|
+
# tests/conftest.py
|
|
7
|
+
pytest_plugins = ["hyperforge.minimal_fixtures"]
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cassette_nua_key(iss: str) -> str:
|
|
18
|
+
"""Return a minimal parseable JWT stub for cassette-replay runs.
|
|
19
|
+
|
|
20
|
+
validate_nua() decodes the middle part of the JWT to extract the ``iss``
|
|
21
|
+
field before making any HTTP call. When cassettes are present VCR
|
|
22
|
+
intercepts that HTTP call, so the key doesn't need to be real — it just
|
|
23
|
+
needs to parse.
|
|
24
|
+
"""
|
|
25
|
+
payload = base64.b64encode(json.dumps({"iss": iss}).encode()).decode().rstrip("=")
|
|
26
|
+
return f"cassette.{payload}.stub"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _VCRTaskExceptionFilter(logging.Filter):
|
|
30
|
+
"""Suppress 'Task exception was never retrieved' asyncio errors from vcrpy.
|
|
31
|
+
|
|
32
|
+
vcrpy's httpx stub creates a background task (_record_responses) that can
|
|
33
|
+
fail with an AssertionError due to a vcrpy/httpx version incompatibility.
|
|
34
|
+
The exception is noisy but harmless in test runs.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
38
|
+
return not (
|
|
39
|
+
record.levelno == logging.ERROR
|
|
40
|
+
and "Task exception was never retrieved" in record.getMessage()
|
|
41
|
+
and "_record_responses" in record.getMessage()
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture(scope="module")
|
|
46
|
+
def vcr_config():
|
|
47
|
+
return {
|
|
48
|
+
# Replaces the actual token with 'DUMMY' in the recorded YAML
|
|
49
|
+
"filter_headers": [
|
|
50
|
+
("Authorization", "DUMMY"),
|
|
51
|
+
("x-nuclia-nuakey", "DUMMY"),
|
|
52
|
+
("x-stf-nuakey", "DUMMY"),
|
|
53
|
+
("x-goog-api-key", "DUMMY"),
|
|
54
|
+
],
|
|
55
|
+
# Redacts specific query parameters like API keys
|
|
56
|
+
"filter_query_parameters": ["api_key", "access_token", "key"],
|
|
57
|
+
# Redacts fields in POST request bodies (e.g., login forms)
|
|
58
|
+
"filter_post_data_parameters": ["password", "client_secret"],
|
|
59
|
+
# Decodes compressed responses so they are human-readable in the cassette
|
|
60
|
+
"decode_compressed_response": True,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.fixture(autouse=True, scope="session")
|
|
65
|
+
def suppress_test_noise() -> None:
|
|
66
|
+
"""Suppress known-noisy log lines that add no diagnostic value in tests."""
|
|
67
|
+
logging.getLogger("hyperforge.memory").setLevel(logging.WARNING)
|
|
68
|
+
logging.getLogger("mcp.server.streamable_http").setLevel(logging.WARNING)
|
|
69
|
+
logging.getLogger("hyperforge.server").setLevel(logging.WARNING)
|
|
70
|
+
logging.getLogger("httpx").setLevel(logging.ERROR)
|
|
71
|
+
logging.getLogger("httpcore.connection").setLevel(logging.ERROR)
|
|
72
|
+
logging.getLogger("httpcore.http11").setLevel(logging.ERROR)
|
|
73
|
+
logging.getLogger("asyncio").setLevel(logging.INFO)
|
|
74
|
+
|
|
75
|
+
logging.getLogger("asyncio").addFilter(_VCRTaskExceptionFilter())
|
hyperforge/models.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Dict,
|
|
7
|
+
List,
|
|
8
|
+
Literal,
|
|
9
|
+
Optional,
|
|
10
|
+
Union,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from nuclia.lib.nua_responses import Image, StoredLearningConfiguration
|
|
14
|
+
from nucliadb_models.resource import (
|
|
15
|
+
ConversationFieldData,
|
|
16
|
+
FileFieldData,
|
|
17
|
+
GenericFieldData,
|
|
18
|
+
LinkFieldData,
|
|
19
|
+
TextFieldData,
|
|
20
|
+
)
|
|
21
|
+
from nucliadb_models.search import CatalogFacetsResponse
|
|
22
|
+
from pydantic import BaseModel, Field
|
|
23
|
+
|
|
24
|
+
from hyperforge import PROMPT_ENVIRONMENT, logger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Metadata:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class KnowledgeGraph:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Reason:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class NucliaDBMemoryConfig(BaseModel):
|
|
40
|
+
key: Optional[str] = None
|
|
41
|
+
url: str
|
|
42
|
+
kbid: str
|
|
43
|
+
internal: bool = True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MemoryConfig(BaseModel):
|
|
47
|
+
nucliadb: Optional[NucliaDBMemoryConfig] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Rule(BaseModel):
|
|
51
|
+
prompt: Optional[str] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Rules(BaseModel):
|
|
55
|
+
rules: List[Union[Rule, str]] = Field(
|
|
56
|
+
default_factory=list,
|
|
57
|
+
description="List of rules that the workflow should follow. Each rule can be a string or a Rule object with a prompt.",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Facets(BaseModel):
|
|
62
|
+
chunks: Dict[str, int]
|
|
63
|
+
fields: Dict[str, int]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Source(BaseModel):
|
|
67
|
+
id: str
|
|
68
|
+
description: str
|
|
69
|
+
labels: Dict[str, List[str]]
|
|
70
|
+
facets_native: CatalogFacetsResponse
|
|
71
|
+
paragraph_facets: Dict[str, int]
|
|
72
|
+
learning_configuration: StoredLearningConfiguration
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class CitationMetadata(BaseModel):
|
|
76
|
+
context_id: str = Field(
|
|
77
|
+
description="ID of the context this citation refers to",
|
|
78
|
+
)
|
|
79
|
+
origin_urls: list[str] = Field(
|
|
80
|
+
default_factory=list,
|
|
81
|
+
description="List of origin URLs that this citation refers to",
|
|
82
|
+
)
|
|
83
|
+
chunk_index: Optional[int] = Field(
|
|
84
|
+
default=None,
|
|
85
|
+
description="Index of the chunk in the context's chunks list. This is only set for chunk-level citations.",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AnswerCitations(BaseModel):
|
|
90
|
+
metadata: dict[str, CitationMetadata] = Field(
|
|
91
|
+
default_factory=dict,
|
|
92
|
+
description="Map of citation_id to citation metadata. block-AA",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class VegaLiteVisualization(BaseModel):
|
|
97
|
+
type: Literal["vega_lite"] = "vega_lite"
|
|
98
|
+
vega_lite_obj: Dict[str, Any] = Field(
|
|
99
|
+
default_factory=dict,
|
|
100
|
+
description="The Vega-Lite Object defining the visualization. Previously validated against the Vega-Lite schema.",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# If we do server-side rendering in the future, we can add fields like:
|
|
104
|
+
# svg: Optional[str] = ...
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# For once we add more visualization types, we can use a Discriminator
|
|
108
|
+
# Visualization = Annotated[Union[VegaLiteVisualization,NewType], Discriminator("type")]
|
|
109
|
+
# For now, we only have one type.
|
|
110
|
+
Visualization = Union[VegaLiteVisualization]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Step(BaseModel):
|
|
114
|
+
original_question_uuid: Optional[str]
|
|
115
|
+
actual_question_uuid: Optional[str]
|
|
116
|
+
module: str
|
|
117
|
+
title: str
|
|
118
|
+
value: Optional[str] = None
|
|
119
|
+
agent_path: str
|
|
120
|
+
reason: Optional[str] = None
|
|
121
|
+
timeit: float
|
|
122
|
+
input_nuclia_tokens: Optional[float]
|
|
123
|
+
output_nuclia_tokens: Optional[float]
|
|
124
|
+
error: Optional[str] = None
|
|
125
|
+
|
|
126
|
+
def __str__(self):
|
|
127
|
+
return f"({self.timeit:.2f}s) {self.module}: {self.title} \n {self.value} \n {self.reason} \n NT:({self.input_nuclia_tokens}:{self.output_nuclia_tokens})"
|
|
128
|
+
|
|
129
|
+
def markdown(self):
|
|
130
|
+
return f"""
|
|
131
|
+
## {self.title}
|
|
132
|
+
|
|
133
|
+
{self.value}
|
|
134
|
+
|
|
135
|
+
- reason: {self.reason}
|
|
136
|
+
- timeit: {self.timeit}
|
|
137
|
+
- input_tokens: {self.input_nuclia_tokens}
|
|
138
|
+
- output_tokens: {self.output_nuclia_tokens}
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ChunkImages(BaseModel):
|
|
143
|
+
table: Optional[str]
|
|
144
|
+
chunk: Optional[str]
|
|
145
|
+
page: Optional[str]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
FieldTypes = Union[
|
|
149
|
+
TextFieldData,
|
|
150
|
+
ConversationFieldData,
|
|
151
|
+
FileFieldData,
|
|
152
|
+
LinkFieldData,
|
|
153
|
+
GenericFieldData,
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class Chunk(BaseModel):
|
|
158
|
+
chunk_id: str
|
|
159
|
+
title: Optional[str] = None
|
|
160
|
+
source: Optional[str] = None
|
|
161
|
+
text: str
|
|
162
|
+
labels: List[str] = Field(default_factory=list)
|
|
163
|
+
url: List[str] = Field(default_factory=list)
|
|
164
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
165
|
+
action: Optional[str] = Field(
|
|
166
|
+
default=None,
|
|
167
|
+
description="agent and function called to get this chunk.",
|
|
168
|
+
)
|
|
169
|
+
origin_url: Optional[str] = Field(
|
|
170
|
+
default=None,
|
|
171
|
+
description="URL at the origin of the resource from which this chunk was extracted.",
|
|
172
|
+
)
|
|
173
|
+
origin_agent: Optional[str] = Field(
|
|
174
|
+
default=None,
|
|
175
|
+
description="Agent that originated this chunk. This is useful to keep track of the provenance of the information ",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def render(
|
|
179
|
+
self,
|
|
180
|
+
citations_id: Optional[str] = None,
|
|
181
|
+
) -> str:
|
|
182
|
+
if citations_id:
|
|
183
|
+
lines = [f"## Chunk: [{citations_id}] {self.title or self.chunk_id}"]
|
|
184
|
+
else:
|
|
185
|
+
lines = [f"## Chunk: {self.title or self.chunk_id}"]
|
|
186
|
+
if self.action:
|
|
187
|
+
lines.append(f"Result of running: {self.action}")
|
|
188
|
+
if self.labels:
|
|
189
|
+
lines.append(f"Tags: {', '.join(self.labels)}")
|
|
190
|
+
if self.url:
|
|
191
|
+
lines.append(f"URLs: {', '.join(self.url)}")
|
|
192
|
+
lines.append(f"``` {self.text} ```\n")
|
|
193
|
+
return "\n".join(lines)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class Prompt(BaseModel):
|
|
197
|
+
prompt: str
|
|
198
|
+
resources: List[str] = Field(default_factory=list)
|
|
199
|
+
links: List[str] = Field(default_factory=list)
|
|
200
|
+
description: Optional[str] = None
|
|
201
|
+
|
|
202
|
+
def render(self) -> str:
|
|
203
|
+
lines = ["## Prompt"]
|
|
204
|
+
if self.description:
|
|
205
|
+
lines.append(f"Description: {self.description}")
|
|
206
|
+
if self.resources:
|
|
207
|
+
lines.append(f"Resources: {', '.join(self.resources)}")
|
|
208
|
+
if self.links:
|
|
209
|
+
lines.append(f"Links: {', '.join(self.links)}")
|
|
210
|
+
lines.append(f"```PROMPT\n{self.prompt}\n```\n")
|
|
211
|
+
return "\n".join(lines)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class Answer(BaseModel):
|
|
215
|
+
answer: str
|
|
216
|
+
original_question_uuid: Optional[str]
|
|
217
|
+
actual_question_uuid: Optional[str]
|
|
218
|
+
module: str
|
|
219
|
+
agent_path: str
|
|
220
|
+
data_visualizations: Optional[list[Visualization]] = None
|
|
221
|
+
citations: Optional[AnswerCitations] = None
|
|
222
|
+
chunks: Optional[list[Chunk]] = None
|
|
223
|
+
structured: Optional[list[str]] = None
|
|
224
|
+
images: Optional[Dict[str, Image]] = None
|
|
225
|
+
image_urls: Optional[list[str]] = None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
CONTEXT_TEMPLATE = """
|
|
229
|
+
|
|
230
|
+
{% if con.citations_id is not none -%}
|
|
231
|
+
{% for chunk in con.chunks %}
|
|
232
|
+
{{chunk.render(citations_id=con.citations_id ~ "-" ~ loop.index0)}}
|
|
233
|
+
{% endfor -%}
|
|
234
|
+
{% else -%}
|
|
235
|
+
{% for chunk in con.chunks %}
|
|
236
|
+
{{chunk.render()}}
|
|
237
|
+
{% endfor -%}
|
|
238
|
+
{% endif -%}
|
|
239
|
+
|
|
240
|
+
{% if con.structured | length > 0 -%}
|
|
241
|
+
## Extra structured info:
|
|
242
|
+
{% for structured in con.structured %}
|
|
243
|
+
{{structured}}
|
|
244
|
+
{% endfor -%}
|
|
245
|
+
{% endif -%}
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
CONTEXT_PROMPT_TEMPLATE = PROMPT_ENVIRONMENT.from_string(CONTEXT_TEMPLATE)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class Context(BaseModel):
|
|
252
|
+
id: str = Field(
|
|
253
|
+
default_factory=lambda: uuid.uuid4().hex,
|
|
254
|
+
description="Unique identifier for this context instance",
|
|
255
|
+
)
|
|
256
|
+
original_question_uuid: Optional[str]
|
|
257
|
+
actual_question_uuid: Optional[str]
|
|
258
|
+
question: str
|
|
259
|
+
chunks: List[Chunk] = Field(default_factory=list)
|
|
260
|
+
images: Dict[str, Image] = Field(default_factory=dict)
|
|
261
|
+
prompts: List[Prompt] = Field(default_factory=list)
|
|
262
|
+
structured: List[str] = Field(default_factory=list)
|
|
263
|
+
source: str
|
|
264
|
+
agent: str
|
|
265
|
+
# XXX: This is not actually a summary, but an answer attempt for now!
|
|
266
|
+
summary: str = Field(
|
|
267
|
+
default="",
|
|
268
|
+
description="Partial or full answer to the question, generated by the context validation step inside a context agent.",
|
|
269
|
+
)
|
|
270
|
+
agent_id: str = ""
|
|
271
|
+
title: Optional[str] = None
|
|
272
|
+
missing: Optional[str] = None
|
|
273
|
+
citations: list[str] | None = Field(
|
|
274
|
+
default=None,
|
|
275
|
+
description="List of chunk IDs that were considered relevant in the context validation step.",
|
|
276
|
+
)
|
|
277
|
+
citations_id: Optional[str] = Field(
|
|
278
|
+
default=None,
|
|
279
|
+
description="Block ID used for citations in this context.",
|
|
280
|
+
)
|
|
281
|
+
image_urls: List[str] = Field(
|
|
282
|
+
default_factory=list,
|
|
283
|
+
description="List of image URLs associated with this context.",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def answer_summary_markdown(self) -> str:
|
|
287
|
+
return "# {question}\n\n {summary}".format(
|
|
288
|
+
question=self.question, summary=self.summary
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def context_markdown(self) -> str:
|
|
292
|
+
return CONTEXT_PROMPT_TEMPLATE.render(con=self)
|
|
293
|
+
|
|
294
|
+
def stats(self) -> Dict[str, int | str | None]:
|
|
295
|
+
return {
|
|
296
|
+
"chunks": len(self.chunks),
|
|
297
|
+
"images": len(self.images),
|
|
298
|
+
"structured": len(self.structured),
|
|
299
|
+
"source": self.source,
|
|
300
|
+
"question": self.question,
|
|
301
|
+
"agent": self.agent,
|
|
302
|
+
"summary": self.summary,
|
|
303
|
+
"title": self.title,
|
|
304
|
+
"missing": self.missing,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
def prune_to_citations(self) -> None:
|
|
308
|
+
if self.citations is None:
|
|
309
|
+
logger.warning(
|
|
310
|
+
"Cannot prune context as no citations are available.",
|
|
311
|
+
extra={
|
|
312
|
+
"agent": self.agent,
|
|
313
|
+
"source": self.source,
|
|
314
|
+
"agent_id": self.agent_id,
|
|
315
|
+
},
|
|
316
|
+
)
|
|
317
|
+
return
|
|
318
|
+
self.chunks = [
|
|
319
|
+
chunk for chunk in self.chunks if chunk.chunk_id in self.citations
|
|
320
|
+
]
|
|
321
|
+
self.structured = [
|
|
322
|
+
s
|
|
323
|
+
for i, s in enumerate(self.structured)
|
|
324
|
+
if f"structured-{i}" in self.citations
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class HistoryQuestionAnswer(BaseModel):
|
|
329
|
+
question: str
|
|
330
|
+
answer: str
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class TrackingInfo(BaseModel):
|
|
334
|
+
rao_id: str
|
|
335
|
+
session: str
|
|
336
|
+
message: str
|