Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [
]
dependencies = [
# Core framework & DI
"aidial-client (>=0.8.0,<0.9.0)", # DIAL API client
"aidial-client (>=0.10.0,<0.11.0)", # DIAL API client
"aidial-sdk[telemetry]>=0.32.0,<0.33.0", # DIAL integration SDK
"pydantic>=2.12.4,<3.0.0", # Data validation
"pydantic-settings>=2.12.0,<3.0.0", # Settings management
Expand Down
38 changes: 28 additions & 10 deletions src/quickapp/common/json_schema_converter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any

from fastmcp.utilities.json_schema import dereference_refs
from jsonref import replace_refs # type: ignore[import-untyped]
from jsonref import JsonRefError, replace_refs # type: ignore[import-untyped]

from quickapp.config.tools.base import (
ConfigurableSchemaArray,
Expand All @@ -22,6 +22,23 @@ class JsonSchemaConverter:
to jsonref.replace_refs() for schemas with circular references.
"""

@staticmethod
def _contains_ref(obj: Any) -> bool:
if isinstance(obj, dict):
if "$ref" in obj:
return True
return any(JsonSchemaConverter._contains_ref(v) for v in obj.values())
if isinstance(obj, list):
return any(JsonSchemaConverter._contains_ref(v) for v in obj)
return False

@staticmethod
def _dereference_with_jsonref(schema_dict: dict[str, Any]) -> dict[str, Any]:
dereferenced = replace_refs(schema_dict, proxies=False, lazy_load=False)
if not isinstance(dereferenced, dict):
raise TypeError(f"Expected dict from replace_refs, got {type(dereferenced).__name__}")
return dereferenced

@staticmethod
def _normalize_type(type_field: str | list[str] | None) -> str | None:
"""
Expand Down Expand Up @@ -206,14 +223,15 @@ def convert_schema_to_properties(
try:
schema_dict = dereference_refs(schema_dict)
except RecursionError:
# Circular inline $ref (e.g. "#/properties/node") causes RecursionError
# in fastmcp 3.x's _strip_discriminator. Fall back to bare jsonref which
# produces circular Python dicts that _convert_properties handles via id().
dereferenced = replace_refs(schema_dict, proxies=False, lazy_load=False)
if not isinstance(dereferenced, dict):
raise TypeError(
f"Expected dict from replace_refs, got {type(dereferenced).__name__}"
)
schema_dict = dereferenced
# Circular inline $ref can blow the stack in fastmcp post-processing.
schema_dict = JsonSchemaConverter._dereference_with_jsonref(schema_dict)
else:
if JsonSchemaConverter._contains_ref(schema_dict):
# fastmcp may leave inline circular $ref unresolved; jsonref materializes
# circular Python dicts that _convert_properties handles via id().
try:
schema_dict = JsonSchemaConverter._dereference_with_jsonref(schema_dict)
except JsonRefError:
pass # broken refs -> ValueError during conversion

return JsonSchemaConverter._convert_properties(schema_dict)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Admin-configured integration test context.

The three items are:

- AlphaReader
- BetaIndexer
- GammaMapper
49 changes: 41 additions & 8 deletions src/tests/integration_tests/test_orchestrator_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@

@pytest.mark.integration
@e2e_test(
config_file_set="lazy_admin_context",
config_file_set="no_additional_tools",
models_applicable_for_test=_LAZY_CONTEXT_MODELS,
runs=1,
runs=2,
include_rest_toolset=False,
application_context_files=[
_DOCS / "ontologies.pdf",
Expand All @@ -36,12 +36,36 @@
similarity_threshold=0.8,
)
.add_user_message(
user_message="enlist all tools that you have",
answer=[
"I have access to internal_attachments_available_context and "
"internal_attachments_get_content tools for listing and loading admin context files.",
"Available tools: internal_attachments_available_context (lists admin files) and "
"internal_attachments_get_content (loads a specific file).",
user_message="enlist tool names that you have",
answer=["""
Tool names I have access to (with what they do):

- `functions.internal_attachments_available_context` — list admin-configured context attachments available to load
- `functions.internal_attachments_get_content` — load one attachment by its provided `url`
- `functions.internal_skills_read_skill` — read the full content of an agent skill/instructions

DIAL file storage tools:

- `functions.internal_file_list` — list files/folders under a path (optionally recursive to a depth)
- `functions.internal_file_find` — find files by glob pattern (metadata-only walk)
- `functions.internal_file_read_lines` — read specific line ranges from a text file
- `functions.internal_file_search` — search for a substring across a file or folder tree
- `functions.internal_file_write` — create a new UTF-8 text file
- `functions.internal_file_edit` — replace a unique substring in an existing UTF-8 text file
- `functions.internal_file_copy` — copy a file server-side
- `functions.internal_file_move` — move/rename a file
- `functions.internal_file_delete` — delete a file

Parallel wrapper:

- `multi_tool_use.parallel` — run multiple tool calls in parallel (only for the developer-defined tools above)

If you tell me what you want to do (e.g., “search all files for X”, “read a PDF attachment”, “create/edit a report file”), I’ll suggest the best tool(s) to use.
"""],
tool_calls=[
ToolCall(
ToolNames.INTERNAL_ATTACHMENTS_AVAILABLE_CONTEXT.value, min_calls=0, max_calls=4
),
],
)
.add_user_message(
Expand Down Expand Up @@ -71,6 +95,15 @@
8. TopBraid Composer
9. OntoGraf
10. VOWL""",
"""
1. Protégé
2. Topbraid Composer
3. Ontostudio
4. Fluent Editor
5. VocBench
6. Swoop
7. Obo-edit
""",
],
),
)
Expand Down
4 changes: 4 additions & 0 deletions src/tests/integration_tests/test_runner/app_test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
_PyInterpreterSettings,
)
from quickapp.mcp_tooling import MCPToolingModule
from quickapp.orchestrator_attachment_strategies.lazy_on_demand.lazy_on_demand_strategy_module import (
LazyOnDemandStrategyModule,
)
from quickapp.predefined_tooling import PredefinedToolingModule
from quickapp.rest_api_tooling import RestApiToolingModule
from quickapp.shared import shared_module
Expand Down Expand Up @@ -87,6 +90,7 @@ def get_app(cls, port: int = 8081):
SkillsModule(),
DialPromptSkillsModule(),
DialFilesToolingModule(),
LazyOnDemandStrategyModule(),
]
)
dial_settings = DialSettings(url=TestConfig.get_mock_dial_core_url(port))
Expand Down
Loading
Loading