licos-agent-runtime 0.3.2__tar.gz → 0.3.4__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.
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/PKG-INFO +1 -1
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/pyproject.toml +1 -1
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/app.py +0 -25
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_config.py +0 -5
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_verification.py +3 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/service.py +2 -16
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/session_memory.py +7 -43
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/verify.py +4 -1
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_harness.py +9 -102
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_runtime_config.py +7 -0
- licos_agent_runtime-0.3.2/src/licos_agent_runtime/project_history.py +0 -69
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/.gitignore +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/README.md +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/__init__.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/__main__.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/agent_canvas.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/agent_config.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/agent_graph.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/agent_input.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/attachments.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/context.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/credit_availability.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/errors.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/event_store.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/event_subscriptions.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/graph_contract.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/graph_loader.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_approval.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_execution.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_planning.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/logging.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/model_identity.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/model_usage.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/observability.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/openai.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/output_files.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/parser.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/payload.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/project_agent.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/project_files.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/project_skills.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/run_config.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/runtime_config.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/session_files.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/stream_protocol.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/stream_runner.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/telemetry.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/trace_records.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/workflow_canvas.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/workflow_protocol.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_agent_config.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_agent_graph.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_app_cors.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_attachment_inputs.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_context.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_credit_availability.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_credit_availability_routes.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_deployment_layout.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_event_delivery_api.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_event_subscriptions.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_execution_errors.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_graph_contract.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_graph_loader.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_model_identity.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_model_usage.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_observability.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_openai.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_parser.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_payload.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_project_files.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_project_skills.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_run_config.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_service_stream_protocol.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_static_assets.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_stream_protocol.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_stream_runner.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_trace_records.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_verify.py +0 -0
- {licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_workflow_protocol.py +0 -0
|
@@ -469,31 +469,6 @@ def create_app(service: GraphService | None = None) -> FastAPI:
|
|
|
469
469
|
del request
|
|
470
470
|
return runtime_service.agent_config_info()
|
|
471
471
|
|
|
472
|
-
@app.get("/agent/history/{session_id}")
|
|
473
|
-
async def http_agent_history(
|
|
474
|
-
session_id: str,
|
|
475
|
-
request: Request,
|
|
476
|
-
limit: int = Query(20, ge=1, le=100),
|
|
477
|
-
offset: int = Query(0, ge=0),
|
|
478
|
-
) -> dict[str, Any]:
|
|
479
|
-
ctx = new_context(method="agent_history", headers=request.headers)
|
|
480
|
-
try:
|
|
481
|
-
return await runtime_service.conversation_history(session_id, ctx, limit, offset)
|
|
482
|
-
except LookupError as exc:
|
|
483
|
-
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
484
|
-
except ValueError as exc:
|
|
485
|
-
raise HTTPException(status_code=401, detail=str(exc)) from exc
|
|
486
|
-
|
|
487
|
-
@app.delete("/agent/history/{session_id}")
|
|
488
|
-
async def http_agent_history_delete(session_id: str, request: Request) -> dict[str, Any]:
|
|
489
|
-
ctx = new_context(method="agent_history_delete", headers=request.headers)
|
|
490
|
-
try:
|
|
491
|
-
return await runtime_service.delete_conversation_history(session_id, ctx)
|
|
492
|
-
except LookupError as exc:
|
|
493
|
-
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
494
|
-
except ValueError as exc:
|
|
495
|
-
raise HTTPException(status_code=401, detail=str(exc)) from exc
|
|
496
|
-
|
|
497
472
|
@app.get("/run_records")
|
|
498
473
|
async def http_run_records(
|
|
499
474
|
request: Request,
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_config.py
RENAMED
|
@@ -25,10 +25,6 @@ class MemorySettings(_StrictModel):
|
|
|
25
25
|
idle_ttl_seconds: int = Field(default=3600, ge=1)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class HistorySettings(_StrictModel):
|
|
29
|
-
enabled: bool = False
|
|
30
|
-
|
|
31
|
-
|
|
32
28
|
class PlanningSettings(_StrictModel):
|
|
33
29
|
mode: Literal["off", "auto", "always"] = "auto"
|
|
34
30
|
|
|
@@ -81,7 +77,6 @@ class ThinkingSettings(_StrictModel):
|
|
|
81
77
|
class HarnessSettings(_StrictModel):
|
|
82
78
|
version: Literal[1]
|
|
83
79
|
memory: MemorySettings = Field(default_factory=MemorySettings)
|
|
84
|
-
history: HistorySettings = Field(default_factory=HistorySettings)
|
|
85
80
|
planning: PlanningSettings = Field(default_factory=PlanningSettings)
|
|
86
81
|
context: ContextSettings = Field(default_factory=ContextSettings)
|
|
87
82
|
execution: ExecutionSettings = Field(default_factory=ExecutionSettings)
|
|
@@ -18,11 +18,14 @@ _LINK = re.compile(r"\[([^\]]+)\]\((https?://[^\s)]+)\)")
|
|
|
18
18
|
_FILE_LINK_LABEL = re.compile(r"下载|文件|附件|文档|报告|download|file|document|report", re.I)
|
|
19
19
|
_DELIVERY_CLAIM = re.compile(r"已上传|上传成功|可下载|下载链接\s*[::]")
|
|
20
20
|
_NEGATION = re.compile(r"未|没有|无法|不能|不会|尚无|尚未|无可用|不提供")
|
|
21
|
+
_CONDITIONAL = re.compile(r"若|如果|假如|假设|如需")
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def _claims_file_delivery(answer: str) -> bool:
|
|
24
25
|
for clause in re.split(r"[,。;;\n]", answer):
|
|
25
26
|
for claim in _DELIVERY_CLAIM.finditer(clause):
|
|
27
|
+
if _CONDITIONAL.search(clause[:claim.start()]):
|
|
28
|
+
continue
|
|
26
29
|
if not _NEGATION.search(clause[max(0, claim.start() - 12):claim.start()]):
|
|
27
30
|
return True
|
|
28
31
|
return False
|
|
@@ -300,7 +300,7 @@ class GraphService:
|
|
|
300
300
|
result = await asyncio.wait_for(
|
|
301
301
|
invocation, timeout=snapshot.harness.execution.max_duration_seconds
|
|
302
302
|
) if snapshot and snapshot.harness else await invocation
|
|
303
|
-
|
|
303
|
+
turn.commit(extract_answer(result))
|
|
304
304
|
else:
|
|
305
305
|
payload = materialize_input_files(payload)
|
|
306
306
|
recorder.set_root_input(payload)
|
|
@@ -511,7 +511,7 @@ class GraphService:
|
|
|
511
511
|
else:
|
|
512
512
|
await consume()
|
|
513
513
|
if answers:
|
|
514
|
-
|
|
514
|
+
turn.commit("".join(answers) if run_opt.stream_mode == "messages" else answers[-1])
|
|
515
515
|
except BaseException as exc:
|
|
516
516
|
await queue.put(exc)
|
|
517
517
|
finally:
|
|
@@ -906,20 +906,6 @@ class GraphService:
|
|
|
906
906
|
**read_agent_config(),
|
|
907
907
|
}
|
|
908
908
|
|
|
909
|
-
async def conversation_history(
|
|
910
|
-
self, session_id: str, ctx: Context, limit: int, offset: int
|
|
911
|
-
) -> dict[str, Any]:
|
|
912
|
-
snapshot = self._agent_snapshot(ctx)
|
|
913
|
-
if snapshot is None or snapshot.harness is None or not snapshot.harness.history.enabled:
|
|
914
|
-
raise LookupError("Project database conversation history is disabled")
|
|
915
|
-
return await self._session_memory.history_page(ctx, snapshot.harness, session_id, limit, offset)
|
|
916
|
-
|
|
917
|
-
async def delete_conversation_history(self, session_id: str, ctx: Context) -> dict[str, Any]:
|
|
918
|
-
snapshot = self._agent_snapshot(ctx)
|
|
919
|
-
if snapshot is None or snapshot.harness is None or not snapshot.harness.history.enabled:
|
|
920
|
-
raise LookupError("Project database conversation history is disabled")
|
|
921
|
-
return await self._session_memory.history_delete(ctx, snapshot.harness, session_id)
|
|
922
|
-
|
|
923
909
|
def agent_canvas(self) -> dict[str, Any]:
|
|
924
910
|
if is_agent_project():
|
|
925
911
|
return agent_canvas_response()
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/session_memory.py
RENAMED
|
@@ -12,7 +12,6 @@ from typing import Any, AsyncIterator
|
|
|
12
12
|
|
|
13
13
|
from .context import Context
|
|
14
14
|
from .harness_config import HarnessSettings
|
|
15
|
-
from .project_history import ProjectHistory
|
|
16
15
|
from .session_files import session_user_id
|
|
17
16
|
from .stream_protocol import extract_prompt_text
|
|
18
17
|
|
|
@@ -35,17 +34,10 @@ class MemoryTurn:
|
|
|
35
34
|
user_text: str | None = None
|
|
36
35
|
_session: _Session | None = None
|
|
37
36
|
_window: int = 0
|
|
38
|
-
_history: ProjectHistory | None = None
|
|
39
|
-
_user_id: str = ""
|
|
40
|
-
_session_id: str = ""
|
|
41
|
-
_run_id: str = ""
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
def commit(self, answer: str) -> None:
|
|
44
39
|
if self._session is None or not self.user_text or not answer.strip():
|
|
45
40
|
return
|
|
46
|
-
if self._history is not None:
|
|
47
|
-
await self._history.save(self._user_id, self._session_id, self._run_id, self.user_text, answer)
|
|
48
|
-
return
|
|
49
41
|
self._session.turns.append((self.user_text, answer))
|
|
50
42
|
while len(self._session.turns) > self._window:
|
|
51
43
|
self._session.turns.popleft()
|
|
@@ -56,13 +48,13 @@ class SessionMemory:
|
|
|
56
48
|
def __init__(self) -> None:
|
|
57
49
|
self._sessions: OrderedDict[SessionKey, _Session] = OrderedDict()
|
|
58
50
|
self._generation = 0
|
|
59
|
-
self._history = ProjectHistory()
|
|
60
51
|
|
|
61
52
|
@staticmethod
|
|
62
53
|
def _eligible(request: dict[str, Any], settings: HarnessSettings | None) -> bool:
|
|
63
54
|
return bool(
|
|
64
55
|
settings
|
|
65
|
-
and
|
|
56
|
+
and settings.memory.enabled
|
|
57
|
+
and settings.memory.recent_turns
|
|
66
58
|
and isinstance(request.get("session_id"), str)
|
|
67
59
|
and request["session_id"].strip()
|
|
68
60
|
and not request.get("messages")
|
|
@@ -115,21 +107,11 @@ class SessionMemory:
|
|
|
115
107
|
if session.generation != self._generation or now - session.touched > settings.memory.idle_ttl_seconds:
|
|
116
108
|
session.turns.clear()
|
|
117
109
|
session.generation = self._generation
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
session.turns.clear()
|
|
121
|
-
turns = (
|
|
122
|
-
await self._history.recent(user_id, session_id, settings.memory.recent_turns)
|
|
123
|
-
if settings.memory.enabled and settings.memory.recent_turns else []
|
|
124
|
-
)
|
|
125
|
-
else:
|
|
126
|
-
user_id = ""
|
|
127
|
-
while len(session.turns) > settings.memory.recent_turns:
|
|
128
|
-
session.turns.popleft()
|
|
129
|
-
turns = session.turns if settings.memory.enabled else []
|
|
110
|
+
while len(session.turns) > settings.memory.recent_turns:
|
|
111
|
+
session.turns.popleft()
|
|
130
112
|
session.touched = now
|
|
131
113
|
history: list[dict[str, str]] = []
|
|
132
|
-
for user, assistant in turns:
|
|
114
|
+
for user, assistant in session.turns:
|
|
133
115
|
history.extend(({"role": "user", "content": user}, {"role": "assistant", "content": assistant}))
|
|
134
116
|
current = payload.get("messages")
|
|
135
117
|
prepared = dict(payload)
|
|
@@ -142,25 +124,7 @@ class SessionMemory:
|
|
|
142
124
|
if text is None:
|
|
143
125
|
value = payload.get("input")
|
|
144
126
|
text = value if isinstance(value, str) else None
|
|
145
|
-
yield MemoryTurn(
|
|
146
|
-
prepared, text, session, settings.memory.recent_turns,
|
|
147
|
-
self._history if settings.history.enabled else None,
|
|
148
|
-
user_id, session_id, ctx.run_id,
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
async def history_page(
|
|
152
|
-
self, ctx: Context, settings: HarnessSettings, session_id: str, limit: int, offset: int
|
|
153
|
-
) -> dict[str, Any]:
|
|
154
|
-
if not settings.history.enabled:
|
|
155
|
-
raise ValueError("Project database conversation history is disabled")
|
|
156
|
-
return await self._history.page(session_user_id(ctx), session_id, limit, offset)
|
|
157
|
-
|
|
158
|
-
async def history_delete(
|
|
159
|
-
self, ctx: Context, settings: HarnessSettings, session_id: str
|
|
160
|
-
) -> dict[str, Any]:
|
|
161
|
-
if not settings.history.enabled:
|
|
162
|
-
raise ValueError("Project database conversation history is disabled")
|
|
163
|
-
return await self._history.delete(session_user_id(ctx), session_id)
|
|
127
|
+
yield MemoryTurn(prepared, text, session, settings.memory.recent_turns)
|
|
164
128
|
|
|
165
129
|
def clear(self) -> None:
|
|
166
130
|
self._generation += 1
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import os
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
from uuid import uuid4
|
|
@@ -116,7 +117,9 @@ def verify_project(*, cases_path: str, base_url: str, output_path: str, request_
|
|
|
116
117
|
report["request_timeout_seconds"] = timeout
|
|
117
118
|
report["project"] = validate_project()
|
|
118
119
|
cases = json.loads(Path(cases_path).read_text(encoding="utf-8"))
|
|
119
|
-
|
|
120
|
+
token = os.environ.get("LICOS_AGENT_VERIFY_BEARER_TOKEN")
|
|
121
|
+
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
122
|
+
with httpx.Client(base_url=base_url.rstrip("/") + "/", timeout=timeout, headers=headers, trust_env=False) as client:
|
|
120
123
|
report["cases"] = verify_cases(client, cases)
|
|
121
124
|
report["status"] = "passed"
|
|
122
125
|
return report
|
|
@@ -230,7 +230,7 @@ class HarnessTests(unittest.TestCase):
|
|
|
230
230
|
{"input": "first"}, ctx, settings, root) as turn:
|
|
231
231
|
started.set()
|
|
232
232
|
await release.wait()
|
|
233
|
-
|
|
233
|
+
turn.commit("answer-first")
|
|
234
234
|
|
|
235
235
|
async def second():
|
|
236
236
|
async with memory.use({"session_id": "same", "input": "second"},
|
|
@@ -348,107 +348,6 @@ class HarnessTests(unittest.TestCase):
|
|
|
348
348
|
|
|
349
349
|
asyncio.run(run())
|
|
350
350
|
|
|
351
|
-
def test_project_database_history_persists_across_runtime_restart_and_isolates_users(self):
|
|
352
|
-
rows = []
|
|
353
|
-
|
|
354
|
-
def query(_table, *, filters, limit, offset=0, **_kwargs):
|
|
355
|
-
scoped = [row for row in rows if all(row[item["field"]] == item["value"] for item in filters)]
|
|
356
|
-
scoped.reverse()
|
|
357
|
-
return {"data": scoped[offset:offset + limit], "count": len(scoped)}
|
|
358
|
-
|
|
359
|
-
def insert(_table, *, row, returning):
|
|
360
|
-
self.assertFalse(returning)
|
|
361
|
-
rows.append({**row, "created_at": len(rows)})
|
|
362
|
-
return {"affectedRows": 1}
|
|
363
|
-
|
|
364
|
-
def delete(_table, *, filters, returning):
|
|
365
|
-
self.assertFalse(returning)
|
|
366
|
-
before = len(rows)
|
|
367
|
-
rows[:] = [row for row in rows if not all(row[item["field"]] == item["value"] for item in filters)]
|
|
368
|
-
return {"affectedRows": before - len(rows)}
|
|
369
|
-
|
|
370
|
-
async def run():
|
|
371
|
-
with tempfile.TemporaryDirectory(dir=".tmp") as directory:
|
|
372
|
-
root = Path(directory)
|
|
373
|
-
(root / "config").mkdir()
|
|
374
|
-
(root / "config/agent_llm_config.json").write_text(json.dumps({
|
|
375
|
-
"harness": {"version": 1, "planning": {"mode": "off"},
|
|
376
|
-
"history": {"enabled": True}},
|
|
377
|
-
}), encoding="utf-8")
|
|
378
|
-
model = HarnessModel()
|
|
379
|
-
graph = build_agent_graph(model, system_prompt="test")
|
|
380
|
-
service = GraphService()
|
|
381
|
-
|
|
382
|
-
async def send(text, user="user-1", session="same"):
|
|
383
|
-
ctx = new_context("run")
|
|
384
|
-
ctx.identity.current_user_id = user
|
|
385
|
-
await service.run({"input": text, "session_id": session}, ctx)
|
|
386
|
-
|
|
387
|
-
with (
|
|
388
|
-
patch.dict(os.environ, {"LICOS_PROJECT_PATH": str(root), "LICOS_WORKSPACE_PATH": str(root)}),
|
|
389
|
-
patch("licos_agent_runtime.service.is_agent_project", return_value=True),
|
|
390
|
-
patch("licos_agent_runtime.project_history.database.query", side_effect=query),
|
|
391
|
-
patch("licos_agent_runtime.project_history.database.insert", side_effect=insert),
|
|
392
|
-
patch("licos_agent_runtime.project_history.database.delete", side_effect=delete),
|
|
393
|
-
patch.object(GraphService, "_get_graph", return_value=graph),
|
|
394
|
-
):
|
|
395
|
-
for number in range(1, 5):
|
|
396
|
-
await send(f"turn-{number}")
|
|
397
|
-
self.assertEqual(len(rows), 4)
|
|
398
|
-
service = GraphService()
|
|
399
|
-
await send("turn-5")
|
|
400
|
-
self.assertEqual(
|
|
401
|
-
[message.content for message in model.seen[-1] if isinstance(message, HumanMessage)],
|
|
402
|
-
["turn-2", "turn-3", "turn-4", "turn-5"],
|
|
403
|
-
)
|
|
404
|
-
await send("another-user", user="user-2")
|
|
405
|
-
self.assertEqual(
|
|
406
|
-
[message.content for message in model.seen[-1] if isinstance(message, HumanMessage)],
|
|
407
|
-
["another-user"],
|
|
408
|
-
)
|
|
409
|
-
await send("another-session", session="other")
|
|
410
|
-
self.assertEqual(
|
|
411
|
-
[message.content for message in model.seen[-1] if isinstance(message, HumanMessage)],
|
|
412
|
-
["another-session"],
|
|
413
|
-
)
|
|
414
|
-
ctx = new_context("history")
|
|
415
|
-
ctx.identity.current_user_id = "user-1"
|
|
416
|
-
page = await service.conversation_history("same", ctx, 2, 1)
|
|
417
|
-
self.assertEqual(page["count"], 5)
|
|
418
|
-
self.assertEqual([row["user_text"] for row in page["data"]], ["turn-4", "turn-3"])
|
|
419
|
-
ctx.identity.current_user_id = "user-2"
|
|
420
|
-
page = await service.conversation_history("same", ctx, 20, 0)
|
|
421
|
-
self.assertEqual([row["user_text"] for row in page["data"]], ["another-user"])
|
|
422
|
-
self.assertEqual((await service.delete_conversation_history("same", ctx))["affectedRows"], 1)
|
|
423
|
-
ctx.identity.current_user_id = "user-1"
|
|
424
|
-
self.assertEqual((await service.conversation_history("same", ctx, 20, 0))["count"], 5)
|
|
425
|
-
|
|
426
|
-
asyncio.run(run())
|
|
427
|
-
|
|
428
|
-
def test_enabled_project_database_history_write_failure_fails_the_run(self):
|
|
429
|
-
async def run():
|
|
430
|
-
with tempfile.TemporaryDirectory(dir=".tmp") as directory:
|
|
431
|
-
root = Path(directory)
|
|
432
|
-
(root / "config").mkdir()
|
|
433
|
-
(root / "config/agent_llm_config.json").write_text(json.dumps({
|
|
434
|
-
"harness": {"version": 1, "planning": {"mode": "off"},
|
|
435
|
-
"history": {"enabled": True}},
|
|
436
|
-
}), encoding="utf-8")
|
|
437
|
-
service = GraphService()
|
|
438
|
-
ctx = new_context("run")
|
|
439
|
-
ctx.identity.current_user_id = "user-1"
|
|
440
|
-
with (
|
|
441
|
-
patch.dict(os.environ, {"LICOS_PROJECT_PATH": str(root), "LICOS_WORKSPACE_PATH": str(root)}),
|
|
442
|
-
patch("licos_agent_runtime.service.is_agent_project", return_value=True),
|
|
443
|
-
patch("licos_agent_runtime.project_history.database.query", return_value={"data": []}),
|
|
444
|
-
patch("licos_agent_runtime.project_history.database.insert", side_effect=RuntimeError("write failed")),
|
|
445
|
-
patch.object(service, "_get_graph", return_value=build_agent_graph(HarnessModel(), system_prompt="test")),
|
|
446
|
-
):
|
|
447
|
-
with self.assertRaisesRegex(RuntimeError, "write failed"):
|
|
448
|
-
await service.run({"input": "hello", "session_id": "same"}, ctx)
|
|
449
|
-
|
|
450
|
-
asyncio.run(run())
|
|
451
|
-
|
|
452
351
|
def test_stream_commits_only_finished_response(self):
|
|
453
352
|
async def run():
|
|
454
353
|
with tempfile.TemporaryDirectory(dir=".tmp") as directory:
|
|
@@ -625,6 +524,14 @@ class HarnessTests(unittest.TestCase):
|
|
|
625
524
|
ToolMessage("Wrote 18 bytes", name="write_file", tool_call_id="w1"),
|
|
626
525
|
AIMessage("文件已写入并核对;没有生成下载链接,也未上传。"),
|
|
627
526
|
]))
|
|
527
|
+
self.assertIsNone(verify_tool_evidence([
|
|
528
|
+
HumanMessage("explain message input"),
|
|
529
|
+
AIMessage("或作为附件上传(若已上传,可继续处理)。如果确实需要一份可下载的文件,我会先上传。"),
|
|
530
|
+
]))
|
|
531
|
+
self.assertIn("verified upload URL", verify_tool_evidence([
|
|
532
|
+
HumanMessage("deliver a file"),
|
|
533
|
+
AIMessage("文件已上传,可以下载。"),
|
|
534
|
+
]))
|
|
628
535
|
self.assertIn("lack a successful upload", verify_tool_evidence([
|
|
629
536
|
HumanMessage("deliver a report"),
|
|
630
537
|
ToolMessage("Wrote 18 bytes", name="write_file", tool_call_id="w1"),
|
|
@@ -44,3 +44,10 @@ class RuntimeConfigTests(unittest.TestCase):
|
|
|
44
44
|
report = json.loads(target.read_text(encoding="utf-8"))
|
|
45
45
|
self.assertEqual(report["status"], "failed")
|
|
46
46
|
self.assertIn("positive finite", report["error"])
|
|
47
|
+
|
|
48
|
+
def test_verification_sends_authenticated_user_token(self):
|
|
49
|
+
with tempfile.TemporaryDirectory() as root, patch.dict(os.environ, {"LICOS_AGENT_VERIFY_BEARER_TOKEN": "dev-user-token"}), patch("licos_agent_runtime.verify.validate_project", return_value={}), patch("licos_agent_runtime.verify.verify_cases", return_value=[]), patch("licos_agent_runtime.verify.httpx.Client") as client:
|
|
50
|
+
cases = Path(root) / "cases.json"
|
|
51
|
+
cases.write_text("[]", encoding="utf-8")
|
|
52
|
+
verify_project(cases_path=str(cases), base_url="http://localhost:5000", output_path=str(Path(root) / "report.json"))
|
|
53
|
+
self.assertEqual(client.call_args.kwargs["headers"], {"Authorization": "Bearer dev-user-token"})
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"""Opt-in completed-turn history stored in the current project database."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import asyncio
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from licos_platform_sdk import database
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
TABLE = "agent_conversation_turns"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _scope(user_id: str, session_id: str) -> list[dict[str, str]]:
|
|
14
|
-
return [
|
|
15
|
-
{"field": "user_id", "op": "eq", "value": user_id},
|
|
16
|
-
{"field": "session_id", "op": "eq", "value": session_id},
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ProjectHistory:
|
|
21
|
-
async def recent(self, user_id: str, session_id: str, limit: int) -> list[tuple[str, str]]:
|
|
22
|
-
result = await asyncio.to_thread(
|
|
23
|
-
database.query,
|
|
24
|
-
TABLE,
|
|
25
|
-
select=["user_text", "assistant_text"],
|
|
26
|
-
filters=_scope(user_id, session_id),
|
|
27
|
-
order_by=[
|
|
28
|
-
{"field": "created_at", "direction": "desc"},
|
|
29
|
-
{"field": "id", "direction": "desc"},
|
|
30
|
-
],
|
|
31
|
-
limit=limit,
|
|
32
|
-
)
|
|
33
|
-
return [(row["user_text"], row["assistant_text"]) for row in reversed(result["data"])]
|
|
34
|
-
|
|
35
|
-
async def save(
|
|
36
|
-
self, user_id: str, session_id: str, run_id: str, user_text: str, assistant_text: str
|
|
37
|
-
) -> None:
|
|
38
|
-
await asyncio.to_thread(
|
|
39
|
-
database.insert,
|
|
40
|
-
TABLE,
|
|
41
|
-
row={
|
|
42
|
-
"id": run_id,
|
|
43
|
-
"user_id": user_id,
|
|
44
|
-
"session_id": session_id,
|
|
45
|
-
"user_text": user_text,
|
|
46
|
-
"assistant_text": assistant_text,
|
|
47
|
-
},
|
|
48
|
-
returning=False,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
async def page(self, user_id: str, session_id: str, limit: int, offset: int) -> dict[str, Any]:
|
|
52
|
-
return await asyncio.to_thread(
|
|
53
|
-
database.query,
|
|
54
|
-
TABLE,
|
|
55
|
-
select=["id", "user_text", "assistant_text", "created_at"],
|
|
56
|
-
filters=_scope(user_id, session_id),
|
|
57
|
-
order_by=[
|
|
58
|
-
{"field": "created_at", "direction": "desc"},
|
|
59
|
-
{"field": "id", "direction": "desc"},
|
|
60
|
-
],
|
|
61
|
-
limit=limit,
|
|
62
|
-
offset=offset,
|
|
63
|
-
count=True,
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
async def delete(self, user_id: str, session_id: str) -> dict[str, Any]:
|
|
67
|
-
return await asyncio.to_thread(
|
|
68
|
-
database.delete, TABLE, filters=_scope(user_id, session_id), returning=False
|
|
69
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/agent_canvas.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/agent_config.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/agent_graph.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/agent_input.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/attachments.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/event_store.py
RENAMED
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/graph_contract.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/graph_loader.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_approval.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_execution.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/harness_planning.py
RENAMED
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/model_identity.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/model_usage.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/observability.py
RENAMED
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/output_files.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/project_agent.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/project_files.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/project_skills.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/run_config.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/runtime_config.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/session_files.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/stream_protocol.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/stream_runner.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/telemetry.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/trace_records.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/workflow_canvas.py
RENAMED
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/src/licos_agent_runtime/workflow_protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_credit_availability_routes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{licos_agent_runtime-0.3.2 → licos_agent_runtime-0.3.4}/tests/test_service_stream_protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|