10xgraph-api 0.7.0__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.
- 10xgraph_api-0.7.0.dist-info/METADATA +618 -0
- 10xgraph_api-0.7.0.dist-info/RECORD +203 -0
- 10xgraph_api-0.7.0.dist-info/WHEEL +5 -0
- 10xgraph_api-0.7.0.dist-info/entry_points.txt +3 -0
- 10xgraph_api-0.7.0.dist-info/licenses/LICENSE +21 -0
- 10xgraph_api-0.7.0.dist-info/top_level.txt +2 -0
- agentflow_cli/__init__.py +69 -0
- tenxgraph_api/__init__.py +23 -0
- tenxgraph_api/cli/__init__.py +6 -0
- tenxgraph_api/cli/capabilities.py +127 -0
- tenxgraph_api/cli/commands/__init__.py +49 -0
- tenxgraph_api/cli/commands/api.py +262 -0
- tenxgraph_api/cli/commands/audit.py +213 -0
- tenxgraph_api/cli/commands/build.py +305 -0
- tenxgraph_api/cli/commands/config.py +71 -0
- tenxgraph_api/cli/commands/demo.py +93 -0
- tenxgraph_api/cli/commands/eval.py +909 -0
- tenxgraph_api/cli/commands/init.py +531 -0
- tenxgraph_api/cli/commands/skills.py +532 -0
- tenxgraph_api/cli/commands/test.py +113 -0
- tenxgraph_api/cli/commands/version.py +47 -0
- tenxgraph_api/cli/config_editor/__init__.py +15 -0
- tenxgraph_api/cli/config_editor/schema.py +566 -0
- tenxgraph_api/cli/config_editor/server.py +240 -0
- tenxgraph_api/cli/config_editor/static/app.css +2 -0
- tenxgraph_api/cli/config_editor/static/app.js +3 -0
- tenxgraph_api/cli/config_editor/static/favicon.svg +11 -0
- tenxgraph_api/cli/config_editor/static/index.html +20 -0
- tenxgraph_api/cli/config_editor/store.py +120 -0
- tenxgraph_api/cli/config_editor/validation.py +377 -0
- tenxgraph_api/cli/constants.py +112 -0
- tenxgraph_api/cli/context.py +46 -0
- tenxgraph_api/cli/core/__init__.py +0 -0
- tenxgraph_api/cli/core/animations.py +550 -0
- tenxgraph_api/cli/core/config.py +279 -0
- tenxgraph_api/cli/core/output.py +625 -0
- tenxgraph_api/cli/core/prompts.py +220 -0
- tenxgraph_api/cli/core/screen.py +287 -0
- tenxgraph_api/cli/core/steps.py +621 -0
- tenxgraph_api/cli/core/theme.py +242 -0
- tenxgraph_api/cli/core/validation.py +251 -0
- tenxgraph_api/cli/exceptions.py +129 -0
- tenxgraph_api/cli/logger.py +84 -0
- tenxgraph_api/cli/main.py +936 -0
- tenxgraph_api/cli/templates/__init__.py +0 -0
- tenxgraph_api/cli/templates/defaults.py +844 -0
- tenxgraph_api/cli/templates/dev/.env.example +1 -0
- tenxgraph_api/cli/templates/dev/10xgraph.json +16 -0
- tenxgraph_api/cli/templates/dev/graph/__init__.py +0 -0
- tenxgraph_api/cli/templates/dev/graph/agent.py +63 -0
- tenxgraph_api/cli/templates/prod/.env.example +75 -0
- tenxgraph_api/cli/templates/prod/.pre-commit-config.yaml +50 -0
- tenxgraph_api/cli/templates/prod/.python-version +1 -0
- tenxgraph_api/cli/templates/prod/10xgraph.json +42 -0
- tenxgraph_api/cli/templates/prod/auth/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/auth/agent_auth.py +46 -0
- tenxgraph_api/cli/templates/prod/evals/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/evals/user_simulator_eval.py +76 -0
- tenxgraph_api/cli/templates/prod/evals/weather_agents_eval.py +53 -0
- tenxgraph_api/cli/templates/prod/graph/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/graph/agent.py +50 -0
- tenxgraph_api/cli/templates/prod/graph/state.py +5 -0
- tenxgraph_api/cli/templates/prod/graph/thread_name_generator.py +7 -0
- tenxgraph_api/cli/templates/prod/graph/tools/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/graph/tools/weather_tool.py +43 -0
- tenxgraph_api/cli/templates/prod/graph/validators/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/graph/validators/lifecyle.py +107 -0
- tenxgraph_api/cli/templates/prod/graph/validators/manager.py +9 -0
- tenxgraph_api/cli/templates/prod/graph/validators/validators.py +21 -0
- tenxgraph_api/cli/templates/prod/pyproject.toml +79 -0
- tenxgraph_api/cli/templates/prod/tests/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/tests/conftest.py +6 -0
- tenxgraph_api/cli/templates/prod/tests/test_agent_eval.py +78 -0
- tenxgraph_api/cli/templates/prod/tests/test_catalog_tools.py +47 -0
- tenxgraph_api/cli/templates/prod/tests/test_graph_nodes.py +57 -0
- tenxgraph_api/cli/templates/skills/10xgraph/SKILL.md +92 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/agents-and-tools.md +389 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/api-client.md +61 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/api-configuration.md +75 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/api-settings-and-middleware.md +89 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/architecture.md +91 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/auth-and-authorization.md +149 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/callbacks-and-command.md +283 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/checkpointing-and-threads.md +63 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/cli-commands.md +73 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/client-auth-and-errors.md +94 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/client-messages-invoke-stream.md +115 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/client-threads-memory-files.md +80 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/context-id-background.md +80 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/dependency-injection.md +58 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/evaluation.md +500 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/id-and-thread-name-generators.md +69 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/media-and-files.md +66 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/memory-and-store.md +70 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/prebuilt-agents-and-tools.md +394 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/production-runtime.md +56 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/providers-and-adapters.md +89 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/publishers-and-runtime-protocols.md +84 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/rate-limiting.md +194 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/realtime.md +172 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/remote-tools.md +55 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/rest-api-and-errors.md +97 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/security-and-validators.md +86 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/state-and-messages.md +68 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/state-graph.md +232 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/stream-emitter.md +144 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/streaming.md +53 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/testing-and-evaluation.md +64 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/unit-testing.md +202 -0
- tenxgraph_api/cli/templates/skills/copilot/10xgraph.instructions.md +108 -0
- tenxgraph_api/py.typed +0 -0
- tenxgraph_api/src/__init__.py +0 -0
- tenxgraph_api/src/app/__init__.py +13 -0
- tenxgraph_api/src/app/core/__init__.py +15 -0
- tenxgraph_api/src/app/core/auth/__init__.py +0 -0
- tenxgraph_api/src/app/core/auth/auth_backend.py +38 -0
- tenxgraph_api/src/app/core/auth/authorization.py +379 -0
- tenxgraph_api/src/app/core/auth/base_auth.py +27 -0
- tenxgraph_api/src/app/core/auth/jwt_auth.py +122 -0
- tenxgraph_api/src/app/core/auth/ownership_resolver.py +170 -0
- tenxgraph_api/src/app/core/auth/permissions.py +354 -0
- tenxgraph_api/src/app/core/auth/request_config.py +130 -0
- tenxgraph_api/src/app/core/auth/route_guard.py +105 -0
- tenxgraph_api/src/app/core/config/__init__.py +0 -0
- tenxgraph_api/src/app/core/config/graph_config.py +532 -0
- tenxgraph_api/src/app/core/config/media_settings.py +72 -0
- tenxgraph_api/src/app/core/config/sentry_config.py +60 -0
- tenxgraph_api/src/app/core/config/settings.py +202 -0
- tenxgraph_api/src/app/core/config/setup_logs.py +65 -0
- tenxgraph_api/src/app/core/config/setup_middleware.py +483 -0
- tenxgraph_api/src/app/core/config/worker_middleware.py +99 -0
- tenxgraph_api/src/app/core/exceptions/__init__.py +17 -0
- tenxgraph_api/src/app/core/exceptions/general_exception.py +58 -0
- tenxgraph_api/src/app/core/exceptions/handle_errors.py +363 -0
- tenxgraph_api/src/app/core/exceptions/resources_exceptions.py +72 -0
- tenxgraph_api/src/app/core/exceptions/user_exception.py +58 -0
- tenxgraph_api/src/app/core/middleware/__init__.py +25 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/__init__.py +15 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/base.py +27 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/factory.py +98 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/keying.py +133 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/memory.py +98 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/middleware.py +90 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/redis.py +115 -0
- tenxgraph_api/src/app/core/middleware/request_limits.py +154 -0
- tenxgraph_api/src/app/core/middleware/security_headers.py +239 -0
- tenxgraph_api/src/app/core/utils/__init__.py +14 -0
- tenxgraph_api/src/app/core/utils/log_sanitizer.py +216 -0
- tenxgraph_api/src/app/loader.py +516 -0
- tenxgraph_api/src/app/main.py +180 -0
- tenxgraph_api/src/app/routers/__init__.py +4 -0
- tenxgraph_api/src/app/routers/ag_ui/__init__.py +10 -0
- tenxgraph_api/src/app/routers/ag_ui/converter.py +195 -0
- tenxgraph_api/src/app/routers/ag_ui/event_mapper.py +353 -0
- tenxgraph_api/src/app/routers/ag_ui/router.py +90 -0
- tenxgraph_api/src/app/routers/ag_ui/service.py +193 -0
- tenxgraph_api/src/app/routers/checkpointer/__init__.py +1 -0
- tenxgraph_api/src/app/routers/checkpointer/router.py +482 -0
- tenxgraph_api/src/app/routers/checkpointer/schemas/__init__.py +1 -0
- tenxgraph_api/src/app/routers/checkpointer/schemas/checkpointer_schemas.py +121 -0
- tenxgraph_api/src/app/routers/checkpointer/services/__init__.py +1 -0
- tenxgraph_api/src/app/routers/checkpointer/services/checkpointer_service.py +300 -0
- tenxgraph_api/src/app/routers/evals/__init__.py +6 -0
- tenxgraph_api/src/app/routers/evals/router.py +38 -0
- tenxgraph_api/src/app/routers/evals/services/__init__.py +1 -0
- tenxgraph_api/src/app/routers/evals/services/eval_report_service.py +354 -0
- tenxgraph_api/src/app/routers/graph/__init__.py +6 -0
- tenxgraph_api/src/app/routers/graph/realtime_guard.py +161 -0
- tenxgraph_api/src/app/routers/graph/router.py +821 -0
- tenxgraph_api/src/app/routers/graph/schemas/__init__.py +1 -0
- tenxgraph_api/src/app/routers/graph/schemas/graph_schemas.py +386 -0
- tenxgraph_api/src/app/routers/graph/services/__init__.py +1 -0
- tenxgraph_api/src/app/routers/graph/services/graph_service.py +1219 -0
- tenxgraph_api/src/app/routers/graph/services/multimodal_preprocessor.py +164 -0
- tenxgraph_api/src/app/routers/media/__init__.py +506 -0
- tenxgraph_api/src/app/routers/media/router.py +242 -0
- tenxgraph_api/src/app/routers/media/schemas.py +39 -0
- tenxgraph_api/src/app/routers/ping/__init__.py +4 -0
- tenxgraph_api/src/app/routers/ping/router.py +28 -0
- tenxgraph_api/src/app/routers/setup_router.py +60 -0
- tenxgraph_api/src/app/routers/store/__init__.py +4 -0
- tenxgraph_api/src/app/routers/store/router.py +200 -0
- tenxgraph_api/src/app/routers/store/schemas/__init__.py +28 -0
- tenxgraph_api/src/app/routers/store/schemas/store_schemas.py +200 -0
- tenxgraph_api/src/app/routers/store/services/__init__.py +4 -0
- tenxgraph_api/src/app/routers/store/services/store_service.py +203 -0
- tenxgraph_api/src/app/tasks/__init__.py +0 -0
- tenxgraph_api/src/app/tasks/user_tasks.py +38 -0
- tenxgraph_api/src/app/utils/__init__.py +12 -0
- tenxgraph_api/src/app/utils/callable_helper.py +26 -0
- tenxgraph_api/src/app/utils/media/__init__.py +15 -0
- tenxgraph_api/src/app/utils/media/extractor.py +172 -0
- tenxgraph_api/src/app/utils/media/pipeline.py +83 -0
- tenxgraph_api/src/app/utils/parse_output.py +17 -0
- tenxgraph_api/src/app/utils/response_helper.py +129 -0
- tenxgraph_api/src/app/utils/schemas/__init__.py +11 -0
- tenxgraph_api/src/app/utils/schemas/output_schemas.py +28 -0
- tenxgraph_api/src/app/utils/schemas/user_schemas.py +13 -0
- tenxgraph_api/src/app/utils/snowflake_id_generator.py +90 -0
- tenxgraph_api/src/app/utils/swagger_helper.py +160 -0
- tenxgraph_api/src/app/utils/telemetry_store.py +123 -0
- tenxgraph_api/src/app/utils/thread_name_generator.py +304 -0
- tenxgraph_api/src/app/worker.py +91 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GOOGLE_API_KEY=""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"agent": "graph.agent:app",
|
|
3
|
+
"env": ".env",
|
|
4
|
+
"auth": null,
|
|
5
|
+
"remote_tools": [],
|
|
6
|
+
"ag_ui": {
|
|
7
|
+
"enabled": false
|
|
8
|
+
},
|
|
9
|
+
"thread_name_generator": null,
|
|
10
|
+
"evaluation": {
|
|
11
|
+
"directory": "evals",
|
|
12
|
+
"output_dir": "eval_reports",
|
|
13
|
+
"threshold": 0.75,
|
|
14
|
+
"timestamp_files": true
|
|
15
|
+
}
|
|
16
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from tenxgraph.core.state import AgentState, StreamEmitter
|
|
5
|
+
from tenxgraph.prebuilt.agent import ReactAgent
|
|
6
|
+
from tenxgraph.storage.checkpointer import InMemoryCheckpointer
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
load_dotenv()
|
|
11
|
+
|
|
12
|
+
checkpointer = InMemoryCheckpointer()
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def call_weather_api(location: str) -> str:
|
|
18
|
+
return f"The weather in {location} is sunny"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_weather(
|
|
22
|
+
location: str,
|
|
23
|
+
tool_call_id: str | None = None,
|
|
24
|
+
state: AgentState | None = None,
|
|
25
|
+
emit: StreamEmitter | None = None,
|
|
26
|
+
) -> str:
|
|
27
|
+
"""Get the current weather for a specific location."""
|
|
28
|
+
if tool_call_id:
|
|
29
|
+
logger.info(f"Tool call ID: {tool_call_id}")
|
|
30
|
+
if state and hasattr(state, "context"):
|
|
31
|
+
logger.info(f"Number of messages in context: {len(state.context)}") # type: ignore
|
|
32
|
+
|
|
33
|
+
if emit:
|
|
34
|
+
emit.progress(f"Fetching weather for {location}")
|
|
35
|
+
|
|
36
|
+
return call_weather_api(location)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
react_agent = ReactAgent(
|
|
40
|
+
model="google/gemini-2.5-flash",
|
|
41
|
+
provider="google",
|
|
42
|
+
system_prompt=[
|
|
43
|
+
{
|
|
44
|
+
"role": "system",
|
|
45
|
+
"content": """
|
|
46
|
+
You are a helpful assistant.
|
|
47
|
+
Your task is to assist the user in finding information and answering questions.
|
|
48
|
+
Use tools when they help answer the user.
|
|
49
|
+
""",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"role": "user",
|
|
53
|
+
"content": f"Today Date is {datetime.now().strftime('%Y-%m-%d')}",
|
|
54
|
+
}, # Inject current date into system prompt
|
|
55
|
+
],
|
|
56
|
+
trim_context=True,
|
|
57
|
+
tools=[get_weather],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
app = react_agent.compile(
|
|
62
|
+
checkpointer=checkpointer,
|
|
63
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#################################
|
|
2
|
+
###### App Config ###############
|
|
3
|
+
#################################
|
|
4
|
+
APP_NAME="$agent_name"
|
|
5
|
+
APP_VERSION="0.1.0"
|
|
6
|
+
MODE="production"
|
|
7
|
+
LOG_LEVEL="INFO"
|
|
8
|
+
IS_DEBUG="false"
|
|
9
|
+
SUMMARY="$agent_name Backend"
|
|
10
|
+
|
|
11
|
+
#################################
|
|
12
|
+
###### CORS Config ##############
|
|
13
|
+
#################################
|
|
14
|
+
# TODO: In production, restrict origins and allowed hosts
|
|
15
|
+
ORIGINS="*"
|
|
16
|
+
ALLOWED_HOST="*"
|
|
17
|
+
|
|
18
|
+
#################################
|
|
19
|
+
###### Paths ####################
|
|
20
|
+
#################################
|
|
21
|
+
ROOT_PATH="/"
|
|
22
|
+
# API docs (and /openapi.json) are served only when a path is set. Keep them off in
|
|
23
|
+
# production; set e.g. DOCS_PATH="/docs" for a staging deployment.
|
|
24
|
+
DOCS_PATH=""
|
|
25
|
+
REDOCS_PATH=""
|
|
26
|
+
|
|
27
|
+
#################################
|
|
28
|
+
###### Request Limits ###########
|
|
29
|
+
#################################
|
|
30
|
+
MAX_REQUEST_SIZE=10485760 # 10MB
|
|
31
|
+
|
|
32
|
+
#################################
|
|
33
|
+
###### Security Headers #########
|
|
34
|
+
#################################
|
|
35
|
+
SECURITY_HEADERS_ENABLED="true"
|
|
36
|
+
HSTS_ENABLED="true"
|
|
37
|
+
HSTS_MAX_AGE=31536000
|
|
38
|
+
HSTS_INCLUDE_SUBDOMAINS="true"
|
|
39
|
+
HSTS_PRELOAD="false"
|
|
40
|
+
FRAME_OPTIONS="DENY"
|
|
41
|
+
CONTENT_TYPE_OPTIONS="nosniff"
|
|
42
|
+
XSS_PROTECTION="1; mode=block"
|
|
43
|
+
REFERRER_POLICY="strict-origin-when-cross-origin"
|
|
44
|
+
|
|
45
|
+
#################################
|
|
46
|
+
###### Snowflake ID Config ######
|
|
47
|
+
#################################
|
|
48
|
+
SNOWFLAKE_EPOCH=1609459200000
|
|
49
|
+
SNOWFLAKE_NODE_ID=1
|
|
50
|
+
SNOWFLAKE_WORKER_ID=2
|
|
51
|
+
|
|
52
|
+
#################################
|
|
53
|
+
###### Sentry Config ############
|
|
54
|
+
#################################
|
|
55
|
+
SENTRY_DSN=""
|
|
56
|
+
|
|
57
|
+
##[IF_REDIS]##
|
|
58
|
+
#################################
|
|
59
|
+
###### Redis Config #############
|
|
60
|
+
#################################
|
|
61
|
+
REDIS_URL=""
|
|
62
|
+
##[/IF_REDIS]##
|
|
63
|
+
|
|
64
|
+
##[IF_JWT]##
|
|
65
|
+
#################################
|
|
66
|
+
###### JWT Config ###############
|
|
67
|
+
#################################
|
|
68
|
+
JWT_SECRET_KEY=""
|
|
69
|
+
JWT_ALGORITHM="HS256"
|
|
70
|
+
##[/IF_JWT]##
|
|
71
|
+
|
|
72
|
+
#################################
|
|
73
|
+
###### API Keys #################
|
|
74
|
+
#################################
|
|
75
|
+
GOOGLE_API_KEY=""
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-yaml
|
|
6
|
+
exclude: '^(tests|docs|examples)/'
|
|
7
|
+
- id: trailing-whitespace
|
|
8
|
+
exclude: '^(tests|docs|examples)/'
|
|
9
|
+
- id: check-added-large-files
|
|
10
|
+
args: ['--maxkb=100']
|
|
11
|
+
exclude: '^(tests|docs|examples)/'
|
|
12
|
+
- id: check-ast
|
|
13
|
+
exclude: '^(tests|docs|examples)/'
|
|
14
|
+
- id: check-builtin-literals
|
|
15
|
+
exclude: '^(tests|docs|examples)/'
|
|
16
|
+
- id: check-case-conflict
|
|
17
|
+
exclude: '^(tests|docs|examples)/'
|
|
18
|
+
- id: check-docstring-first
|
|
19
|
+
exclude: '^(tests|docs|examples)/'
|
|
20
|
+
- id: check-merge-conflict
|
|
21
|
+
exclude: '^(tests|docs|examples)/'
|
|
22
|
+
- id: debug-statements
|
|
23
|
+
exclude: '^(tests|docs|examples)/'
|
|
24
|
+
- id: detect-private-key
|
|
25
|
+
exclude: '^(tests|docs|examples)/'
|
|
26
|
+
|
|
27
|
+
- repo: https://github.com/asottile/pyupgrade
|
|
28
|
+
rev: v3.17.0
|
|
29
|
+
hooks:
|
|
30
|
+
- id: pyupgrade
|
|
31
|
+
args: ['--py310-plus']
|
|
32
|
+
exclude: '^(tests|docs|examples)/'
|
|
33
|
+
|
|
34
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
35
|
+
rev: v0.5.7
|
|
36
|
+
hooks:
|
|
37
|
+
- id: ruff-format
|
|
38
|
+
exclude: '^(tests|docs|examples)/'
|
|
39
|
+
- id: ruff
|
|
40
|
+
args: ['--fix', '--exit-non-zero-on-fix']
|
|
41
|
+
exclude: '^(tests|docs|examples)/'
|
|
42
|
+
|
|
43
|
+
- repo: https://github.com/PyCQA/bandit
|
|
44
|
+
rev: 1.7.9
|
|
45
|
+
hooks:
|
|
46
|
+
- id: bandit
|
|
47
|
+
args: ['-c', 'pyproject.toml']
|
|
48
|
+
additional_dependencies: ['bandit[toml]']
|
|
49
|
+
exclude: '^(tests|docs|examples)/'
|
|
50
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"agent": "graph.agent:app",
|
|
3
|
+
"env": ".env",
|
|
4
|
+
"auth": {
|
|
5
|
+
"method": "custom",
|
|
6
|
+
"path": "auth.agent_auth:AgentAuth"
|
|
7
|
+
},
|
|
8
|
+
"remote_tools": [],
|
|
9
|
+
"ag_ui": {
|
|
10
|
+
"enabled": false
|
|
11
|
+
},
|
|
12
|
+
"thread_name_generator": "graph.thread_name_generator:MyNameGenerator",
|
|
13
|
+
"injectq": "graph.agent:container",
|
|
14
|
+
"rate_limit": {
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"backend": "redis",
|
|
17
|
+
"redis": {
|
|
18
|
+
"url": "${REDIS_URL}",
|
|
19
|
+
"prefix": "10xgraph:rate-limit"
|
|
20
|
+
},
|
|
21
|
+
"requests": 100,
|
|
22
|
+
"window": 60,
|
|
23
|
+
"by": "user",
|
|
24
|
+
"trusted_proxy_headers": true,
|
|
25
|
+
"trusted_proxy_hops": 1,
|
|
26
|
+
"fail_open": false,
|
|
27
|
+
"exclude_paths": ["/ping", "/docs", "/redoc", "/openapi.json"]
|
|
28
|
+
},
|
|
29
|
+
"test": {
|
|
30
|
+
"path": "tests",
|
|
31
|
+
"coverage": true,
|
|
32
|
+
"coverage_threshold": 70
|
|
33
|
+
},
|
|
34
|
+
"evaluation": {
|
|
35
|
+
"directory": "evals",
|
|
36
|
+
"output_dir": "eval_reports",
|
|
37
|
+
"threshold": 0.75,
|
|
38
|
+
"parallel": false,
|
|
39
|
+
"max_concurrency": 4,
|
|
40
|
+
"timestamp_files": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from tenxgraph_api import BaseAuth
|
|
5
|
+
from fastapi import HTTPException, Request, Response
|
|
6
|
+
from fastapi.security import HTTPAuthorizationCredentials
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentAuth(BaseAuth):
|
|
13
|
+
"""Verify the caller and return who they are.
|
|
14
|
+
|
|
15
|
+
Every request is denied with 401 until ``authenticate`` checks a real credential.
|
|
16
|
+
|
|
17
|
+
Contract:
|
|
18
|
+
- Return a dict with at least ``user_id``. It becomes the thread and memory owner, so it
|
|
19
|
+
must come from the verified credential, never from a header or body field the client
|
|
20
|
+
sets. Optional ``roles`` / ``scopes`` feed the authorization backend.
|
|
21
|
+
- Raise ``HTTPException(status_code=401)`` for a missing, invalid or expired credential.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self) -> None:
|
|
25
|
+
logger.warning(
|
|
26
|
+
"AgentAuth.authenticate is not implemented yet: every request will get 401. "
|
|
27
|
+
"Implement it in auth/agent_auth.py."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def authenticate(
|
|
31
|
+
self,
|
|
32
|
+
request: Request,
|
|
33
|
+
response: Response,
|
|
34
|
+
credential: HTTPAuthorizationCredentials,
|
|
35
|
+
) -> dict[str, Any] | None:
|
|
36
|
+
if credential is None:
|
|
37
|
+
raise HTTPException(status_code=401, detail="Missing credentials")
|
|
38
|
+
|
|
39
|
+
# Replace this with a real check, for example verifying the bearer token with your
|
|
40
|
+
# identity provider:
|
|
41
|
+
#
|
|
42
|
+
# claims = verify_token(credential.credentials) # raises on a bad token
|
|
43
|
+
# return {"user_id": claims["sub"], "roles": claims.get("roles", [])}
|
|
44
|
+
#
|
|
45
|
+
# Remove the warning in __init__ once this is done.
|
|
46
|
+
raise HTTPException(status_code=401, detail="Authentication is not configured")
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User simulator evaluation example.
|
|
3
|
+
|
|
4
|
+
The CLI detects get_scenarios() and handles everything:
|
|
5
|
+
- runs each scenario through UserSimulator
|
|
6
|
+
- scores goal achievement with an LLM judge
|
|
7
|
+
- produces the same HTML + JSON report as regular eval cases
|
|
8
|
+
|
|
9
|
+
You only need to define your scenarios. No asyncio, no EvalSetBuilder, no boilerplate.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
10xgraph eval evals/user_simulator_eval.py
|
|
13
|
+
10xgraph eval evals/user_simulator_eval.py --parallel --max-concurrency 4
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from tenxgraph.qa.evaluation import ConversationScenario, UserSimulatorConfig
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Agent — the CLI reads `app` from this module if present, otherwise it loads
|
|
20
|
+
# the graph from 10xgraph.json ("agent": "graph.agent:app") automatically.
|
|
21
|
+
# Uncomment and set this if you want to target a specific graph:
|
|
22
|
+
#
|
|
23
|
+
# from graph.agent import app
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Optional: override the simulator model and settings for this file.
|
|
29
|
+
# If omitted, the CLI uses UserSimulatorConfig defaults (gemini-2.5-flash).
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
SIMULATOR_CONFIG = UserSimulatorConfig(
|
|
32
|
+
model="gemini/gemini-2.5-flash",
|
|
33
|
+
max_invocations=8,
|
|
34
|
+
temperature=0.7,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Scenarios — the only thing you need to write.
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
def get_scenarios() -> list[ConversationScenario]:
|
|
43
|
+
return [
|
|
44
|
+
ConversationScenario(
|
|
45
|
+
scenario_id="weather_travel_planning",
|
|
46
|
+
description="User planning a trip wants weather info and packing advice",
|
|
47
|
+
starting_prompt="Hi! I'm planning a trip to Paris this weekend.",
|
|
48
|
+
conversation_plan=(
|
|
49
|
+
"1. Ask about current weather in Paris\n"
|
|
50
|
+
"2. Ask whether to bring a jacket\n"
|
|
51
|
+
"3. Ask about the best time for outdoor sightseeing"
|
|
52
|
+
),
|
|
53
|
+
goals=[
|
|
54
|
+
"User receives weather information for Paris",
|
|
55
|
+
"User gets clothing or packing advice",
|
|
56
|
+
"User learns about outdoor activity timing",
|
|
57
|
+
],
|
|
58
|
+
max_turns=8,
|
|
59
|
+
),
|
|
60
|
+
ConversationScenario(
|
|
61
|
+
scenario_id="flight_booking_assistance",
|
|
62
|
+
description="User wants help finding a flight from London to New York",
|
|
63
|
+
starting_prompt="I need to fly from London to New York next Friday. Can you help?",
|
|
64
|
+
conversation_plan=(
|
|
65
|
+
"1. Share travel dates and seat preferences\n"
|
|
66
|
+
"2. Ask about available flights and prices\n"
|
|
67
|
+
"3. Confirm booking details"
|
|
68
|
+
),
|
|
69
|
+
goals=[
|
|
70
|
+
"User receives flight options for the requested route",
|
|
71
|
+
"User gets pricing information",
|
|
72
|
+
"Booking process is initiated or completed",
|
|
73
|
+
],
|
|
74
|
+
max_turns=10,
|
|
75
|
+
),
|
|
76
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from tenxgraph.qa.evaluation import EvalConfig, EvalSet, EvalSetBuilder
|
|
2
|
+
from tenxgraph.qa.evaluation.config.presets import EvalPresets
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_eval_config() -> EvalConfig:
|
|
6
|
+
"""Evaluation config for this file.
|
|
7
|
+
|
|
8
|
+
EvalPresets provides ready-made criterion bundles. Pick one that matches
|
|
9
|
+
what your agent does, or call EvalPresets.combine() to mix them.
|
|
10
|
+
|
|
11
|
+
Available presets:
|
|
12
|
+
EvalPresets.response_quality() — LLM judge on response accuracy
|
|
13
|
+
EvalPresets.tool_usage() — checks tool calls + response quality
|
|
14
|
+
EvalPresets.conversation_flow() — multi-turn conversation evaluation
|
|
15
|
+
EvalPresets.comprehensive() — all of the above combined
|
|
16
|
+
EvalPresets.quick_check() — fast ROUGE-based check, no LLM cost
|
|
17
|
+
"""
|
|
18
|
+
return EvalPresets.tool_usage(threshold=0.6)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_eval_set() -> EvalSet:
|
|
22
|
+
return (
|
|
23
|
+
EvalSetBuilder(name="weather-agent-regression")
|
|
24
|
+
.add_case(
|
|
25
|
+
query="Hi",
|
|
26
|
+
expected="assistant",
|
|
27
|
+
expected_node_order=["MAIN"],
|
|
28
|
+
name="greeting_response",
|
|
29
|
+
description="The agent responds to a greeting.",
|
|
30
|
+
)
|
|
31
|
+
.add_tool_test(
|
|
32
|
+
query="What is the weather in London?",
|
|
33
|
+
tool_name="get_weather",
|
|
34
|
+
tool_args={"location": "London"},
|
|
35
|
+
expected_response="London",
|
|
36
|
+
case_id="weather_london",
|
|
37
|
+
)
|
|
38
|
+
.add_tool_test(
|
|
39
|
+
query="Tell me the current weather in New York",
|
|
40
|
+
tool_name="get_weather",
|
|
41
|
+
tool_args={"location": "New York"},
|
|
42
|
+
expected_response="New York",
|
|
43
|
+
case_id="weather_new_york",
|
|
44
|
+
)
|
|
45
|
+
.add_tool_test(
|
|
46
|
+
query="How is the weather in Tokyo today?",
|
|
47
|
+
tool_name="get_weather",
|
|
48
|
+
tool_args={"location": "Tokyo"},
|
|
49
|
+
expected_response="Tokyo",
|
|
50
|
+
case_id="weather_tokyo",
|
|
51
|
+
)
|
|
52
|
+
.build()
|
|
53
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from tenxgraph.core.state import MessageContextManager
|
|
4
|
+
from tenxgraph.prebuilt.agent import ReactAgent
|
|
5
|
+
from tenxgraph.storage.checkpointer import InMemoryCheckpointer
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
from graph.state import WeatherState
|
|
9
|
+
from graph.tools.weather_tool import get_weather
|
|
10
|
+
from graph.validators.manager import callback_manager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
load_dotenv()
|
|
14
|
+
|
|
15
|
+
checkpointer = InMemoryCheckpointer()
|
|
16
|
+
|
|
17
|
+
context_manager = MessageContextManager(
|
|
18
|
+
max_messages=20, # last 20 user messages will be kept in context
|
|
19
|
+
remove_tool_msgs=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
react_agent = ReactAgent(
|
|
23
|
+
state=WeatherState(),
|
|
24
|
+
model="google/gemini-2.5-flash",
|
|
25
|
+
provider="google",
|
|
26
|
+
system_prompt=[
|
|
27
|
+
{
|
|
28
|
+
"role": "system",
|
|
29
|
+
"content": """
|
|
30
|
+
You are a helpful assistant.
|
|
31
|
+
Your task is to assist the user in finding information and answering questions.
|
|
32
|
+
User Current Location: {user_location}
|
|
33
|
+
If missing information, ask the user for clarification.
|
|
34
|
+
Use tools when they help answer the user.
|
|
35
|
+
""",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"role": "user",
|
|
39
|
+
"content": f"Today Date is {datetime.now().strftime('%Y-%m-%d')}",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
trim_context=True,
|
|
43
|
+
tools=[get_weather],
|
|
44
|
+
context_manager=context_manager,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
app = react_agent.compile(
|
|
48
|
+
checkpointer=checkpointer,
|
|
49
|
+
callback_manager=callback_manager,
|
|
50
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
from tenxgraph.core.state.stream_emitter import StreamEmitter
|
|
5
|
+
|
|
6
|
+
from graph.state import WeatherState
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
MAX_RETRIES = 3
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def call_weather_api(location: str) -> str:
|
|
15
|
+
is_failed = random.choice([True, False]) # noqa: S311
|
|
16
|
+
if is_failed:
|
|
17
|
+
raise Exception("Failed to fetch weather data due to a simulated API error.")
|
|
18
|
+
return f"The weather in {location} is sunny"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_weather(
|
|
22
|
+
location: str,
|
|
23
|
+
tool_call_id: str | None = None,
|
|
24
|
+
state: WeatherState | None = None,
|
|
25
|
+
emit: StreamEmitter | None = None,
|
|
26
|
+
) -> str:
|
|
27
|
+
"""Get the current weather for a specific location."""
|
|
28
|
+
if tool_call_id:
|
|
29
|
+
logger.info(f"Tool call ID: {tool_call_id}")
|
|
30
|
+
if state and hasattr(state, "context"):
|
|
31
|
+
logger.info(f"Number of messages in context: {len(state.context)}") # type: ignore
|
|
32
|
+
if emit:
|
|
33
|
+
emit.progress("Fetching weather data...")
|
|
34
|
+
|
|
35
|
+
for i in range(MAX_RETRIES):
|
|
36
|
+
try:
|
|
37
|
+
return call_weather_api(location)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(f"Attempt {i + 1} failed: {e}")
|
|
40
|
+
if emit:
|
|
41
|
+
emit.progress(f"Attempt {i + 1} failed, retrying...")
|
|
42
|
+
|
|
43
|
+
return f"Sorry, I couldn't fetch the weather for {location} after multiple attempts."
|
|
File without changes
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Lifecycle hooks for the weather agent graph."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tenxgraph.core.state.message import Message
|
|
7
|
+
from tenxgraph.utils.callbacks import GraphLifecycleContext, GraphLifecycleHook
|
|
8
|
+
|
|
9
|
+
from graph.state import WeatherState
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentLifecycleHook(GraphLifecycleHook[WeatherState]):
|
|
16
|
+
async def on_graph_start(
|
|
17
|
+
self,
|
|
18
|
+
context: GraphLifecycleContext,
|
|
19
|
+
state: WeatherState,
|
|
20
|
+
) -> WeatherState | None:
|
|
21
|
+
"""Hydrate ``WeatherState`` from the org config attached by AgentAuth."""
|
|
22
|
+
return state
|
|
23
|
+
|
|
24
|
+
async def on_graph_end(
|
|
25
|
+
self,
|
|
26
|
+
context: GraphLifecycleContext,
|
|
27
|
+
final_state: WeatherState,
|
|
28
|
+
messages: list[Message],
|
|
29
|
+
total_steps: int,
|
|
30
|
+
) -> WeatherState | None:
|
|
31
|
+
"""Called after successful graph completion, before final state sync.
|
|
32
|
+
|
|
33
|
+
Return a modified WeatherState to persist, or None to keep the current state.
|
|
34
|
+
"""
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
async def on_graph_error( # noqa: PLR0913
|
|
38
|
+
self,
|
|
39
|
+
context: GraphLifecycleContext,
|
|
40
|
+
error: Exception,
|
|
41
|
+
partial_state: WeatherState,
|
|
42
|
+
messages: list[Message],
|
|
43
|
+
step: int,
|
|
44
|
+
node_name: str,
|
|
45
|
+
) -> tuple[WeatherState, str] | None:
|
|
46
|
+
"""Called when an unhandled error escapes the execution loop.
|
|
47
|
+
|
|
48
|
+
Return (modified_state, error_message) to change the persisted error snapshot,
|
|
49
|
+
or None to keep the current error state. The exception is always re-raised.
|
|
50
|
+
"""
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
async def on_interrupt(
|
|
54
|
+
self,
|
|
55
|
+
context: GraphLifecycleContext,
|
|
56
|
+
interrupted_node: str,
|
|
57
|
+
interrupt_type: str,
|
|
58
|
+
state: WeatherState,
|
|
59
|
+
) -> WeatherState | None:
|
|
60
|
+
"""Called when execution pauses at an interrupt point.
|
|
61
|
+
|
|
62
|
+
Return a modified WeatherState to persist at interrupt, or None to keep the current state.
|
|
63
|
+
In-place mutation of the passed state is also supported.
|
|
64
|
+
"""
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
async def on_resume(
|
|
68
|
+
self,
|
|
69
|
+
context: GraphLifecycleContext,
|
|
70
|
+
resumed_node: str,
|
|
71
|
+
state: WeatherState,
|
|
72
|
+
resume_data: dict[str, Any],
|
|
73
|
+
) -> WeatherState | None:
|
|
74
|
+
"""Called when a previously interrupted execution is about to resume.
|
|
75
|
+
|
|
76
|
+
Called before clear_interrupt(). Return a modified WeatherState to continue with,
|
|
77
|
+
or None to use the loaded state unchanged.
|
|
78
|
+
"""
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
async def on_checkpoint(
|
|
82
|
+
self,
|
|
83
|
+
context: GraphLifecycleContext,
|
|
84
|
+
state: WeatherState,
|
|
85
|
+
messages: list[Message],
|
|
86
|
+
is_context_trimmed: bool,
|
|
87
|
+
) -> tuple[WeatherState, list[Message]] | WeatherState | None:
|
|
88
|
+
"""Called before every durable checkpoint write.
|
|
89
|
+
|
|
90
|
+
Return (state, messages) to modify both, WeatherState to modify state only,
|
|
91
|
+
or None to persist without modification.
|
|
92
|
+
"""
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
async def on_state_update(
|
|
96
|
+
self,
|
|
97
|
+
context: GraphLifecycleContext,
|
|
98
|
+
node_name: str,
|
|
99
|
+
old_state: WeatherState,
|
|
100
|
+
new_state: WeatherState,
|
|
101
|
+
step: int,
|
|
102
|
+
) -> WeatherState | None:
|
|
103
|
+
"""Called after each node's result is merged into state.
|
|
104
|
+
|
|
105
|
+
Return a modified WeatherState to replace new_state, or None to use new_state unchanged.
|
|
106
|
+
"""
|
|
107
|
+
return None
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from tenxgraph.utils.callbacks import CallbackManager
|
|
2
|
+
|
|
3
|
+
from .lifecyle import AgentLifecycleHook
|
|
4
|
+
from .validators import prompt_validator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
callback_manager = CallbackManager()
|
|
8
|
+
callback_manager.register_input_validator(prompt_validator)
|
|
9
|
+
callback_manager.register_lifecycle_hook(AgentLifecycleHook())
|