union-app-chat-stream 1.1.6
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.
- package/app/__init__.py +1 -0
- package/app/agent/__init__.py +1 -0
- package/app/agent/capabilities.py +388 -0
- package/app/agent/coordinator/__init__.py +1 -0
- package/app/agent/coordinator/definition.py +50 -0
- package/app/agent/coordinator/output_guard.py +29 -0
- package/app/agent/graph.py +95 -0
- package/app/agent/guardrails.py +30 -0
- package/app/agent/routing.py +81 -0
- package/app/agent/runtime/__init__.py +1 -0
- package/app/agent/runtime/activity.py +393 -0
- package/app/agent/runtime/delegation.py +80 -0
- package/app/agent/runtime/deps.py +34 -0
- package/app/agent/runtime/execution.py +368 -0
- package/app/agent/runtime/model.py +47 -0
- package/app/agent/runtime/model_errors.py +24 -0
- package/app/agent/runtime/session.py +154 -0
- package/app/agent/specialists/__init__.py +1 -0
- package/app/agent/specialists/behavior_risk/__init__.py +1 -0
- package/app/agent/specialists/behavior_risk/definition.py +54 -0
- package/app/agent/specialists/build.py +94 -0
- package/app/agent/specialists/knowledge/__init__.py +1 -0
- package/app/agent/specialists/knowledge/definition.py +38 -0
- package/app/agent/specialists/personal_memory/__init__.py +1 -0
- package/app/agent/specialists/personal_memory/definition.py +35 -0
- package/app/agent/specialists/personal_memory/output_guard.py +55 -0
- package/app/agent/specialists/running_analysis/__init__.py +1 -0
- package/app/agent/specialists/running_analysis/definition.py +46 -0
- package/app/agent/specialists/running_analysis/output_guard.py +38 -0
- package/app/agent/specialists/scheduled_task_draft/__init__.py +8 -0
- package/app/agent/specialists/scheduled_task_draft/definition.py +142 -0
- package/app/agent/specialists/scheduled_task_draft/output_guard.py +81 -0
- package/app/asgi.py +139 -0
- package/app/config/__init__.py +1 -0
- package/app/config/settings.py +67 -0
- package/app/memory/__init__.py +1 -0
- package/app/memory/store.py +154 -0
- package/app/service/rag_service.py +364 -0
- package/app/skills/full-chain-quality-analysis/SKILL.md +22 -0
- package/app/tools/__init__.py +1 -0
- package/app/tools/business.py +183 -0
- package/app/utils/__init__.py +1 -0
- package/app/utils/api_client.py +76 -0
- package/app/utils/control_auth.py +35 -0
- package/app/utils/state_client.py +60 -0
- package/app/views/__init__.py +1 -0
- package/app/views/auth.py +189 -0
- package/app/views/errors.py +19 -0
- package/app/views/routes.py +25 -0
- package/app/views/run_context.py +33 -0
- package/app/views/streaming_runs.py +340 -0
- package/app/views/sync_runs.py +152 -0
- package/deploy/autoconf/templates/env.j2 +23 -0
- package/deploy/autoconf.yml +15 -0
- package/deploy/scripts/healthcheck.sh +12 -0
- package/deploy/scripts/start.sh +80 -0
- package/deploy/scripts/stop.sh +35 -0
- package/knowledge/000036-scenario-offline-function-call-mock-v1.md +134 -0
- package/package.json +21 -0
- package/requirements.txt +10 -0
- package/scripts/healthcheck.sh +4 -0
- package/scripts/start-BJ11.sh +1 -0
- package/scripts/start-BJ12.sh +1 -0
- package/scripts/start-SH20.sh +1 -0
- package/scripts/start-SZ31.sh +1 -0
- package/scripts/stop.sh +4 -0
package/app/asgi.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""创建 Starlette ASGI 应用并组装配置、运行时与 HTTP 路由。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
import logfire
|
|
13
|
+
from dotenv import load_dotenv
|
|
14
|
+
from starlette.applications import Starlette
|
|
15
|
+
|
|
16
|
+
from app.agent.graph import AgentGraph, build_agent_graph
|
|
17
|
+
from app.agent.runtime.execution import ExecutionCoordinator
|
|
18
|
+
from app.agent.runtime.model import build_model
|
|
19
|
+
from app.config.settings import AgentSettings
|
|
20
|
+
from app.views.routes import RouteError, build_routes, http_error
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@lru_cache(maxsize=1)
|
|
24
|
+
def _configure_observability(environment: str) -> None:
|
|
25
|
+
level = getattr(logging, os.getenv("LOG_LEVEL", "INFO").upper(), logging.INFO)
|
|
26
|
+
logging.basicConfig(
|
|
27
|
+
level=level,
|
|
28
|
+
format="%(asctime)s %(levelname)s %(name)s %(message)s",
|
|
29
|
+
)
|
|
30
|
+
logfire.configure(
|
|
31
|
+
send_to_logfire=False,
|
|
32
|
+
service_name="union-py-app",
|
|
33
|
+
environment=environment,
|
|
34
|
+
inspect_arguments=False,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Runtime:
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
settings: AgentSettings,
|
|
43
|
+
http: httpx.AsyncClient,
|
|
44
|
+
graph: AgentGraph,
|
|
45
|
+
rag_service: Any | None,
|
|
46
|
+
) -> None:
|
|
47
|
+
self.settings = settings
|
|
48
|
+
self.http = http
|
|
49
|
+
self.graph = graph
|
|
50
|
+
self.rag_service = rag_service
|
|
51
|
+
self.executions = ExecutionCoordinator(
|
|
52
|
+
max_run_seconds=settings.max_run_seconds,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _rag_service(settings: AgentSettings):
|
|
57
|
+
if not settings.rag_enabled:
|
|
58
|
+
return None
|
|
59
|
+
from app.service.rag_service import RagService
|
|
60
|
+
|
|
61
|
+
return RagService(
|
|
62
|
+
{
|
|
63
|
+
"RAG_ENABLED": settings.rag_enabled,
|
|
64
|
+
"RAG_TOP_K": settings.rag_top_k,
|
|
65
|
+
"RAG_KNOWLEDGE_DIR": settings.rag_knowledge_dir,
|
|
66
|
+
"RAG_COLLECTION": settings.rag_collection,
|
|
67
|
+
"LLM_URL": settings.llm_url,
|
|
68
|
+
"LLM_KEY": settings.llm_key,
|
|
69
|
+
"RAG_EMBEDDING_MODEL": settings.rag_embedding_model,
|
|
70
|
+
"RAG_CHUNK_SIZE": settings.rag_chunk_size,
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def create_app(
|
|
76
|
+
*,
|
|
77
|
+
settings: AgentSettings | None = None,
|
|
78
|
+
model: Any | None = None,
|
|
79
|
+
http: httpx.AsyncClient | None = None,
|
|
80
|
+
rag_service: Any | None = None,
|
|
81
|
+
) -> Starlette:
|
|
82
|
+
environment = os.getenv("APP_ENV", "dev")
|
|
83
|
+
if settings is None:
|
|
84
|
+
load_dotenv(
|
|
85
|
+
os.path.join(
|
|
86
|
+
os.path.dirname(__file__),
|
|
87
|
+
"config",
|
|
88
|
+
"env",
|
|
89
|
+
f".env.{environment}",
|
|
90
|
+
),
|
|
91
|
+
override=False,
|
|
92
|
+
)
|
|
93
|
+
_configure_observability(environment)
|
|
94
|
+
configured = settings or AgentSettings.from_env()
|
|
95
|
+
|
|
96
|
+
@asynccontextmanager
|
|
97
|
+
async def lifespan(app: Starlette):
|
|
98
|
+
shared_http = http or httpx.AsyncClient(
|
|
99
|
+
limits=httpx.Limits(
|
|
100
|
+
max_connections=100,
|
|
101
|
+
max_keepalive_connections=20,
|
|
102
|
+
),
|
|
103
|
+
follow_redirects=False,
|
|
104
|
+
)
|
|
105
|
+
agent_model = model or build_model(configured, shared_http)
|
|
106
|
+
graph = build_agent_graph(
|
|
107
|
+
agent_model,
|
|
108
|
+
configured,
|
|
109
|
+
router_model=(
|
|
110
|
+
model or build_model(configured, shared_http, thinking=False)
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
runtime = Runtime(
|
|
114
|
+
settings=configured,
|
|
115
|
+
http=shared_http,
|
|
116
|
+
graph=graph,
|
|
117
|
+
rag_service=(
|
|
118
|
+
rag_service
|
|
119
|
+
if rag_service is not None
|
|
120
|
+
else _rag_service(configured)
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
app.state.runtime = runtime
|
|
124
|
+
await runtime.executions.startup()
|
|
125
|
+
try:
|
|
126
|
+
yield
|
|
127
|
+
finally:
|
|
128
|
+
await runtime.executions.close()
|
|
129
|
+
if http is None:
|
|
130
|
+
await shared_http.aclose()
|
|
131
|
+
|
|
132
|
+
return Starlette(
|
|
133
|
+
lifespan=lifespan,
|
|
134
|
+
routes=build_routes(),
|
|
135
|
+
exception_handlers={RouteError: http_error},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
app = create_app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""存放应用配置模型及环境配置文件。"""
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""读取并校验应用运行所需的环境变量配置。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _integer(name: str, default: int) -> int:
|
|
10
|
+
try:
|
|
11
|
+
return int(os.getenv(name, str(default)))
|
|
12
|
+
except ValueError:
|
|
13
|
+
return default
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _floating(name: str, default: float) -> float:
|
|
17
|
+
try:
|
|
18
|
+
return float(os.getenv(name, str(default)))
|
|
19
|
+
except ValueError:
|
|
20
|
+
return default
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class AgentSettings:
|
|
25
|
+
union_base_url: str
|
|
26
|
+
llm_url: str
|
|
27
|
+
llm_key: str
|
|
28
|
+
llm_model: str
|
|
29
|
+
llm_context_window: int
|
|
30
|
+
request_limit: int
|
|
31
|
+
tool_calls_limit: int
|
|
32
|
+
input_tokens_limit: int
|
|
33
|
+
output_tokens_limit: int
|
|
34
|
+
tool_timeout_seconds: float
|
|
35
|
+
subagent_timeout_seconds: float
|
|
36
|
+
max_run_seconds: float
|
|
37
|
+
required_permission: str
|
|
38
|
+
rag_enabled: bool
|
|
39
|
+
rag_knowledge_dir: str
|
|
40
|
+
rag_collection: str
|
|
41
|
+
rag_embedding_model: str
|
|
42
|
+
rag_top_k: int
|
|
43
|
+
rag_chunk_size: int
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_env(cls) -> AgentSettings:
|
|
47
|
+
return cls(
|
|
48
|
+
union_base_url=os.getenv("UNION_BASE_URL", "http://127.0.0.1:8080").rstrip("/"),
|
|
49
|
+
llm_url=os.getenv("LLM_URL", "").rstrip("/"),
|
|
50
|
+
llm_key=os.getenv("LLM_KEY", ""),
|
|
51
|
+
llm_model=os.getenv("LLM_MODEL", ""),
|
|
52
|
+
llm_context_window=_integer("LLM_CONTEXT_WINDOW", 131072),
|
|
53
|
+
request_limit=_integer("AGENT_REQUEST_LIMIT", 30),
|
|
54
|
+
tool_calls_limit=_integer("AGENT_TOOL_CALLS_LIMIT", 40),
|
|
55
|
+
input_tokens_limit=_integer("AGENT_INPUT_TOKENS_LIMIT", 120000),
|
|
56
|
+
output_tokens_limit=_integer("AGENT_OUTPUT_TOKENS_LIMIT", 16000),
|
|
57
|
+
tool_timeout_seconds=_floating("AGENT_TOOL_TIMEOUT_SECONDS", 300.0),
|
|
58
|
+
subagent_timeout_seconds=_floating("SUBAGENT_TIMEOUT_SECONDS", 900.0),
|
|
59
|
+
max_run_seconds=_floating("AGENT_MAX_RUN_SECONDS", 900.0),
|
|
60
|
+
required_permission=os.getenv("PERMISSIONS", ""),
|
|
61
|
+
rag_enabled=os.getenv("RAG_ENABLED", "true").lower() in {"1", "true", "yes", "on"},
|
|
62
|
+
rag_knowledge_dir=os.getenv("RAG_KNOWLEDGE_DIR", "knowledge"),
|
|
63
|
+
rag_collection=os.getenv("RAG_COLLECTION", "ops_knowledge"),
|
|
64
|
+
rag_embedding_model=os.getenv("RAG_EMBEDDING_MODEL", "embedding-3"),
|
|
65
|
+
rag_top_k=_integer("RAG_TOP_K", 5),
|
|
66
|
+
rag_chunk_size=_integer("RAG_CHUNK_SIZE", 1200),
|
|
67
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""存放 Agent 个人记忆的存储适配实现。"""
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""将远端记忆接口适配为 Harness 记忆存储协议。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic_ai_harness.memory import (
|
|
8
|
+
MemoryConflictError,
|
|
9
|
+
MemoryFile,
|
|
10
|
+
MemoryMutation,
|
|
11
|
+
MemoryOperation,
|
|
12
|
+
MemoryOperationConflictError,
|
|
13
|
+
MemorySearchMatch,
|
|
14
|
+
MemorySearchResult,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from app.utils.state_client import AgentStateClient, AgentStateError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentMemoryStore:
|
|
21
|
+
"""HTTP adapter for the official SearchableMemoryStore protocol."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, state_client: AgentStateClient) -> None:
|
|
24
|
+
self._state_client = state_client
|
|
25
|
+
|
|
26
|
+
async def _call(self, action: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
27
|
+
body = await self._state_client.post(f"/agent/memoryStore/{action}", payload)
|
|
28
|
+
code = body.get("errorCode")
|
|
29
|
+
if code == "version_conflict":
|
|
30
|
+
raise MemoryConflictError(body.get("errorMsg", "memory version conflict"))
|
|
31
|
+
if code == "operation_conflict":
|
|
32
|
+
raise MemoryOperationConflictError(body.get("errorMsg", "memory operation conflict"))
|
|
33
|
+
if body.get("success") is False:
|
|
34
|
+
raise AgentStateError(body.get("errorMsg", "memory store request failed"))
|
|
35
|
+
return body
|
|
36
|
+
|
|
37
|
+
async def read(self, path: str, *, max_chars: int) -> MemoryFile | None:
|
|
38
|
+
body = await self._call("read", {"path": path, "maxChars": max_chars})
|
|
39
|
+
item = body.get("file")
|
|
40
|
+
if item is None:
|
|
41
|
+
return None
|
|
42
|
+
return MemoryFile(
|
|
43
|
+
content=str(item["content"]),
|
|
44
|
+
version=str(item["version"]),
|
|
45
|
+
operation_id=item.get("operationId"),
|
|
46
|
+
truncated=bool(item.get("truncated", False)),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def list_paths(self, prefix: str = "", *, limit: int) -> list[str]:
|
|
50
|
+
body = await self._call("list", {"prefix": prefix, "limit": limit})
|
|
51
|
+
return [str(path) for path in body.get("paths", [])]
|
|
52
|
+
|
|
53
|
+
async def get_operation(self, operation: MemoryOperation) -> MemoryMutation | None:
|
|
54
|
+
body = await self._call(
|
|
55
|
+
"operation",
|
|
56
|
+
{"operationId": operation.id, "fingerprint": operation.fingerprint},
|
|
57
|
+
)
|
|
58
|
+
return self._mutation(body.get("mutation"))
|
|
59
|
+
|
|
60
|
+
async def write(
|
|
61
|
+
self,
|
|
62
|
+
path: str,
|
|
63
|
+
content: str,
|
|
64
|
+
*,
|
|
65
|
+
expected_version: str | None,
|
|
66
|
+
operation: MemoryOperation | None = None,
|
|
67
|
+
) -> MemoryMutation:
|
|
68
|
+
return self._required_mutation(
|
|
69
|
+
await self._call(
|
|
70
|
+
"write",
|
|
71
|
+
{
|
|
72
|
+
"path": path,
|
|
73
|
+
"content": content,
|
|
74
|
+
"expectedVersion": expected_version,
|
|
75
|
+
"operation": self._operation(operation),
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def delete(
|
|
81
|
+
self,
|
|
82
|
+
path: str,
|
|
83
|
+
*,
|
|
84
|
+
expected_version: str | None,
|
|
85
|
+
operation: MemoryOperation | None = None,
|
|
86
|
+
) -> MemoryMutation:
|
|
87
|
+
return self._required_mutation(
|
|
88
|
+
await self._call(
|
|
89
|
+
"delete",
|
|
90
|
+
{
|
|
91
|
+
"path": path,
|
|
92
|
+
"expectedVersion": expected_version,
|
|
93
|
+
"operation": self._operation(operation),
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async def search(
|
|
99
|
+
self,
|
|
100
|
+
prefix: str,
|
|
101
|
+
query: str,
|
|
102
|
+
*,
|
|
103
|
+
limit: int,
|
|
104
|
+
max_files: int,
|
|
105
|
+
max_chars: int,
|
|
106
|
+
max_file_chars: int,
|
|
107
|
+
) -> MemorySearchResult:
|
|
108
|
+
body = await self._call(
|
|
109
|
+
"search",
|
|
110
|
+
{
|
|
111
|
+
"prefix": prefix,
|
|
112
|
+
"query": query,
|
|
113
|
+
"limit": limit,
|
|
114
|
+
"maxFiles": max_files,
|
|
115
|
+
"maxChars": max_chars,
|
|
116
|
+
"maxFileChars": max_file_chars,
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
result = body.get("result") or {}
|
|
120
|
+
return MemorySearchResult(
|
|
121
|
+
matches=[
|
|
122
|
+
MemorySearchMatch(
|
|
123
|
+
path=str(item["path"]),
|
|
124
|
+
snippet=str(item["snippet"]),
|
|
125
|
+
score=float(item["score"]),
|
|
126
|
+
)
|
|
127
|
+
for item in result.get("matches", [])
|
|
128
|
+
],
|
|
129
|
+
scanned=int(result.get("scanned", 0)),
|
|
130
|
+
truncated=bool(result.get("truncated", False)),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _operation(operation: MemoryOperation | None) -> dict[str, str] | None:
|
|
135
|
+
if operation is None:
|
|
136
|
+
return None
|
|
137
|
+
return {"id": operation.id, "fingerprint": operation.fingerprint}
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def _mutation(value: Any) -> MemoryMutation | None:
|
|
141
|
+
if value is None:
|
|
142
|
+
return None
|
|
143
|
+
return MemoryMutation(
|
|
144
|
+
version=str(value["version"]) if value.get("version") is not None else None,
|
|
145
|
+
replayed=bool(value.get("replayed", False)),
|
|
146
|
+
existed=bool(value.get("existed", False)),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def _required_mutation(cls, body: dict[str, Any]) -> MemoryMutation:
|
|
151
|
+
mutation = cls._mutation(body.get("mutation"))
|
|
152
|
+
if mutation is None:
|
|
153
|
+
raise AgentStateError("state service omitted memory mutation result")
|
|
154
|
+
return mutation
|