qtype 0.1.12__py3-none-any.whl → 0.1.13__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.
- qtype/` +0 -0
- qtype/application/__init__.py +0 -2
- qtype/application/converters/tools_from_api.py +28 -22
- qtype/application/converters/tools_from_module.py +66 -32
- qtype/commands/generate.py +90 -7
- qtype/commands/run.py +116 -44
- qtype/docs/.pages +8 -0
- {docs → qtype/docs}/Concepts/mental-model-and-philosophy.md +1 -1
- qtype/docs/Contributing/.pages +4 -0
- {docs → qtype/docs}/Contributing/index.md +8 -1
- {docs → qtype/docs}/Gallery/dataflow_pipelines.md +3 -2
- {docs → qtype/docs}/Gallery/research_assistant.md +3 -4
- {docs → qtype/docs}/Gallery/simple_chatbot.md +3 -1
- {docs → qtype/docs}/How To/Authentication/configure_aws_authentication.md +2 -2
- {docs → qtype/docs}/How To/Authentication/use_api_key_authentication.md +2 -2
- {docs → qtype/docs}/How To/Command Line Usage/load_multiple_inputs_from_files.md +24 -9
- {docs → qtype/docs}/How To/Command Line Usage/pass_inputs_on_the_cli.md +3 -3
- {docs → qtype/docs}/How To/Command Line Usage/serve_with_auto_reload.md +3 -2
- {docs → qtype/docs}/How To/Data Processing/adjust_concurrency.md +3 -4
- {docs → qtype/docs}/How To/Data Processing/cache_step_results.md +2 -2
- {docs → qtype/docs}/How To/Data Processing/decode_json_xml.md +1 -1
- {docs → qtype/docs}/How To/Data Processing/explode_collections.md +2 -2
- {docs → qtype/docs}/How To/Data Processing/gather_results.md +4 -4
- qtype/docs/How To/Data Processing/invoke_other_flows.md +71 -0
- qtype/docs/How To/Data Processing/load_data_from_athena.md +49 -0
- qtype/docs/How To/Data Processing/read_data_from_files.md +61 -0
- {docs → qtype/docs}/How To/Data Processing/read_sql_databases.md +2 -3
- {docs → qtype/docs}/How To/Data Processing/write_data_to_file.md +1 -2
- {docs → qtype/docs}/How To/Invoke Models/call_large_language_models.md +1 -1
- {docs → qtype/docs}/How To/Invoke Models/create_embeddings.md +1 -1
- {docs → qtype/docs}/How To/Invoke Models/reuse_prompts_with_templates.md +2 -3
- {docs → qtype/docs}/How To/Language Features/include_raw_text_from_other_files.md +2 -1
- {docs → qtype/docs}/How To/Language Features/reference_entities_by_id.md +2 -2
- qtype/docs/How To/Language Features/use_agent_skills.md +29 -0
- {docs → qtype/docs}/How To/Language Features/use_environment_variables.md +2 -1
- qtype/docs/How To/Language Features/use_optional_variables.md +42 -0
- {docs → qtype/docs}/How To/Language Features/use_qtype_mcp.md +4 -4
- {docs → qtype/docs}/How To/Observability & Debugging/trace_calls_with_open_telemetry.md +1 -1
- {docs → qtype/docs}/How To/Observability & Debugging/validate_qtype_yaml.md +3 -2
- {docs → qtype/docs}/How To/Observability & Debugging/visualize_application_architecture.md +1 -1
- {docs → qtype/docs}/How To/Qtype Server/serve_flows_as_apis.md +3 -3
- {docs → qtype/docs}/How To/Qtype Server/serve_flows_as_ui.md +2 -3
- {docs → qtype/docs}/How To/Qtype Server/use_conversational_interfaces.md +1 -4
- {docs → qtype/docs}/How To/Qtype Server/use_variables_with_ui_hints.md +3 -2
- {docs → qtype/docs}/How To/Tools & Integration/bind_tool_inputs_and_outputs.md +1 -2
- {docs → qtype/docs}/How To/Tools & Integration/create_tools_from_openapi_specifications.md +10 -14
- {docs → qtype/docs}/How To/Tools & Integration/create_tools_from_python_modules.md +5 -8
- {docs → qtype/docs}/Reference/cli.md +13 -15
- {docs → qtype/docs}/Reference/plugins.md +4 -0
- {docs → qtype/docs}/Reference/semantic-validation-rules.md +6 -1
- qtype/docs/Tutorials/.pages +1 -0
- {docs → qtype/docs}/Tutorials/01-first-qtype-application.md +3 -2
- {docs → qtype/docs}/Tutorials/02-conversational-chatbot.md +3 -3
- {docs → qtype/docs}/Tutorials/03-structured-data.md +9 -10
- {docs → qtype/docs}/Tutorials/04-tools-and-function-calling.md +12 -19
- {docs → qtype/docs}/components/APITool.md +1 -1
- qtype/docs/components/Aggregate.md +7 -0
- qtype/docs/components/Collect.md +6 -0
- qtype/docs/components/Construct.md +6 -0
- {docs → qtype/docs}/components/DocumentEmbedder.md +0 -1
- {docs → qtype/docs}/components/DocumentSplitter.md +0 -1
- qtype/docs/components/Explode.md +5 -0
- {docs → qtype/docs}/components/FieldExtractor.md +2 -1
- qtype/docs/components/InvokeFlow.md +8 -0
- qtype/docs/components/InvokeTool.md +8 -0
- {docs → qtype/docs}/components/PrimitiveTypeEnum.md +0 -1
- {docs → qtype/docs}/components/Source.md +0 -1
- {docs → qtype/docs}/components/Step.md +0 -1
- {docs → qtype/docs}/components/Tool.md +2 -2
- {docs → qtype/docs}/components/Variable.md +2 -0
- qtype/docs/legacy_how_tos/.pages +6 -0
- qtype/docs/skills/architect/SKILL.md +188 -0
- qtype/docs/skills/architect/references/cheatsheet.md +198 -0
- qtype/docs/skills/architect/references/patterns.md +29 -0
- qtype/docs/stylesheets/extra.css +27 -0
- qtype/dsl/linker.py +8 -0
- qtype/dsl/model.py +177 -84
- qtype/examples/data_processing/athena_query.qtype.yaml +56 -0
- qtype/examples/data_processing/batch_inputs.csv +5 -0
- qtype/examples/data_processing/create_sample_db.py +129 -0
- qtype/examples/data_processing/invoke_other_flows.qtype.yaml +98 -0
- qtype/examples/data_processing/reviews.db +0 -0
- qtype/examples/data_processing/sample_article.txt +1 -0
- qtype/examples/data_processing/sample_documents.jsonl +5 -0
- qtype/examples/language_features/optional_variables.qtype.yaml +32 -0
- qtype/examples/language_features/story_prompt.txt +6 -0
- qtype/examples/legacy/data/customers.csv +6 -0
- qtype/examples/legacy/echo/readme.md +29 -0
- qtype/examples/legacy/qtype_plugin_example.py +51 -0
- qtype/examples/legacy/sample_data.txt +43 -0
- qtype/examples/legacy/vertex/README.md +11 -0
- qtype/examples/research_assistant/tavily.qtype.yaml +216 -0
- {examples → qtype/examples}/tutorials/03_structured_data.qtype.yaml +2 -2
- {examples → qtype/examples}/tutorials/04_tools_and_function_calling.qtype.yaml +5 -5
- qtype/interpreter/base/stream_emitter.py +19 -13
- qtype/interpreter/converters.py +142 -26
- qtype/interpreter/executors/agent_executor.py +2 -3
- qtype/interpreter/executors/aggregate_executor.py +3 -4
- qtype/interpreter/executors/construct_executor.py +15 -15
- qtype/interpreter/executors/doc_to_text_executor.py +1 -3
- qtype/interpreter/executors/field_extractor_executor.py +13 -12
- qtype/interpreter/executors/file_source_executor.py +18 -31
- qtype/interpreter/executors/invoke_embedding_executor.py +1 -4
- qtype/interpreter/executors/invoke_flow_executor.py +2 -2
- qtype/interpreter/executors/invoke_tool_executor.py +19 -18
- qtype/interpreter/executors/llm_inference_executor.py +16 -18
- qtype/interpreter/executors/prompt_template_executor.py +1 -3
- qtype/interpreter/tools/function_tool_helper.py +11 -10
- qtype/interpreter/types.py +89 -4
- qtype/interpreter/typing.py +31 -32
- qtype/mcp/server.py +312 -57
- {schema → qtype/schema}/qtype.schema.json +77 -79
- qtype/semantic/checker.py +19 -0
- qtype/semantic/generate.py +3 -6
- qtype/semantic/model.py +26 -33
- qtype/semantic/resolver.py +7 -0
- qtype/semantic/visualize.py +8 -3
- {qtype-0.1.12.dist-info → qtype-0.1.13.dist-info}/METADATA +47 -46
- qtype-0.1.13.dist-info/RECORD +352 -0
- {qtype-0.1.12.dist-info → qtype-0.1.13.dist-info}/WHEEL +1 -2
- docs/How To/Data Processing/read_data_from_files.md +0 -35
- docs/components/Aggregate.md +0 -8
- docs/components/InvokeFlow.md +0 -8
- docs/components/InvokeTool.md +0 -8
- docs/components/ToolParameter.md +0 -6
- examples/research_assistant/tavily.qtype.yaml +0 -289
- qtype/application/facade.py +0 -177
- qtype-0.1.12.dist-info/RECORD +0 -325
- qtype-0.1.12.dist-info/top_level.txt +0 -1
- {docs → qtype/docs}/Contributing/roadmap.md +0 -0
- {docs → qtype/docs}/Decisions/ADR-001-Chat-vs-Completion-Endpoint-Features.md +0 -0
- {docs → qtype/docs}/Gallery/dataflow_pipelines.mermaid +0 -0
- {docs → qtype/docs}/Gallery/research_assistant.mermaid +0 -0
- {docs → qtype/docs}/Gallery/simple_chatbot.mermaid +0 -0
- {docs → qtype/docs}/How To/Language Features/include_qtype_yaml.md +0 -0
- {docs → qtype/docs}/How To/Observability & Debugging/visualize_example.mermaid +0 -0
- {docs → qtype/docs}/How To/Qtype Server/flow_as_ui.png +0 -0
- {docs → qtype/docs}/Tutorials/example_chat.png +0 -0
- {docs → qtype/docs}/Tutorials/index.md +0 -0
- {docs → qtype/docs}/components/APIKeyAuthProvider.md +0 -0
- {docs → qtype/docs}/components/AWSAuthProvider.md +0 -0
- {docs → qtype/docs}/components/AWSSecretManager.md +0 -0
- {docs → qtype/docs}/components/Agent.md +0 -0
- {docs → qtype/docs}/components/AggregateStats.md +0 -0
- {docs → qtype/docs}/components/Application.md +0 -0
- {docs → qtype/docs}/components/AuthorizationProvider.md +0 -0
- {docs → qtype/docs}/components/AuthorizationProviderList.md +0 -0
- {docs → qtype/docs}/components/BearerTokenAuthProvider.md +0 -0
- {docs → qtype/docs}/components/BedrockReranker.md +0 -0
- {docs → qtype/docs}/components/ChatContent.md +0 -0
- {docs → qtype/docs}/components/ChatMessage.md +0 -0
- {docs → qtype/docs}/components/ConstantPath.md +0 -0
- {docs → qtype/docs}/components/CustomType.md +0 -0
- {docs → qtype/docs}/components/Decoder.md +0 -0
- {docs → qtype/docs}/components/DecoderFormat.md +0 -0
- {docs → qtype/docs}/components/DocToTextConverter.md +0 -0
- {docs → qtype/docs}/components/Document.md +0 -0
- {docs → qtype/docs}/components/DocumentIndex.md +0 -0
- {docs → qtype/docs}/components/DocumentSearch.md +0 -0
- {docs → qtype/docs}/components/DocumentSource.md +0 -0
- {docs → qtype/docs}/components/Echo.md +0 -0
- {docs → qtype/docs}/components/Embedding.md +0 -0
- {docs → qtype/docs}/components/EmbeddingModel.md +0 -0
- {docs → qtype/docs}/components/FileSource.md +0 -0
- {docs → qtype/docs}/components/FileWriter.md +0 -0
- {docs → qtype/docs}/components/Flow.md +0 -0
- {docs → qtype/docs}/components/FlowInterface.md +0 -0
- {docs → qtype/docs}/components/Index.md +0 -0
- {docs → qtype/docs}/components/IndexUpsert.md +0 -0
- {docs → qtype/docs}/components/InvokeEmbedding.md +0 -0
- {docs → qtype/docs}/components/LLMInference.md +0 -0
- {docs → qtype/docs}/components/ListType.md +0 -0
- {docs → qtype/docs}/components/Memory.md +0 -0
- {docs → qtype/docs}/components/MessageRole.md +0 -0
- {docs → qtype/docs}/components/Model.md +0 -0
- {docs → qtype/docs}/components/ModelList.md +0 -0
- {docs → qtype/docs}/components/OAuth2AuthProvider.md +0 -0
- {docs → qtype/docs}/components/PromptTemplate.md +0 -0
- {docs → qtype/docs}/components/PythonFunctionTool.md +0 -0
- {docs → qtype/docs}/components/RAGChunk.md +0 -0
- {docs → qtype/docs}/components/RAGDocument.md +0 -0
- {docs → qtype/docs}/components/RAGSearchResult.md +0 -0
- {docs → qtype/docs}/components/Reranker.md +0 -0
- {docs → qtype/docs}/components/SQLSource.md +0 -0
- {docs → qtype/docs}/components/Search.md +0 -0
- {docs → qtype/docs}/components/SearchResult.md +0 -0
- {docs → qtype/docs}/components/SecretManager.md +0 -0
- {docs → qtype/docs}/components/SecretReference.md +0 -0
- {docs → qtype/docs}/components/TelemetrySink.md +0 -0
- {docs → qtype/docs}/components/ToolList.md +0 -0
- {docs → qtype/docs}/components/TypeList.md +0 -0
- {docs → qtype/docs}/components/VariableList.md +0 -0
- {docs → qtype/docs}/components/VectorIndex.md +0 -0
- {docs → qtype/docs}/components/VectorSearch.md +0 -0
- {docs → qtype/docs}/components/VertexAuthProvider.md +0 -0
- {docs → qtype/docs}/components/Writer.md +0 -0
- {docs → qtype/docs}/example_ui.png +0 -0
- {docs → qtype/docs}/index.md +0 -0
- {docs → qtype/docs}/legacy_how_tos/Configuration/modular-yaml.md +0 -0
- {docs → qtype/docs}/legacy_how_tos/Configuration/phoenix_projects.png +0 -0
- {docs → qtype/docs}/legacy_how_tos/Configuration/phoenix_traces.png +0 -0
- {docs → qtype/docs}/legacy_how_tos/Configuration/reference-by-id.md +0 -0
- {docs → qtype/docs}/legacy_how_tos/Configuration/telemetry-setup.md +0 -0
- {docs → qtype/docs}/legacy_how_tos/Data Types/custom-types.md +0 -0
- {docs → qtype/docs}/legacy_how_tos/Data Types/domain-types.md +0 -0
- {docs → qtype/docs}/legacy_how_tos/Debugging/visualize-apps.md +0 -0
- {docs → qtype/docs}/legacy_how_tos/Tools/api-tools.md +0 -0
- {docs → qtype/docs}/legacy_how_tos/Tools/python-tools.md +0 -0
- {examples → qtype/examples}/authentication/aws_authentication.qtype.yaml +0 -0
- {examples → qtype/examples}/conversational_ai/hello_world_chat.qtype.yaml +0 -0
- {examples → qtype/examples}/conversational_ai/simple_chatbot.qtype.yaml +0 -0
- {examples → qtype/examples}/data_processing/batch_processing.qtype.yaml +0 -0
- {examples → qtype/examples}/data_processing/cache_step_results.qtype.yaml +0 -0
- {examples → qtype/examples}/data_processing/collect_results.qtype.yaml +0 -0
- {examples → qtype/examples}/data_processing/dataflow_pipelines.qtype.yaml +0 -0
- {examples → qtype/examples}/data_processing/decode_json.qtype.yaml +0 -0
- {examples → qtype/examples}/data_processing/explode_items.qtype.yaml +0 -0
- {examples → qtype/examples}/data_processing/read_file.qtype.yaml +0 -0
- {examples → qtype/examples}/invoke_models/create_embeddings.qtype.yaml +0 -0
- {examples → qtype/examples}/invoke_models/simple_llm_call.qtype.yaml +0 -0
- {examples → qtype/examples}/language_features/include_raw.qtype.yaml +0 -0
- {examples → qtype/examples}/language_features/ui_hints.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/bedrock/data_analysis_with_telemetry.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/bedrock/hello_world.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/bedrock/hello_world_chat.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/bedrock/hello_world_chat_with_telemetry.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/bedrock/hello_world_chat_with_thinking.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/bedrock/hello_world_completion.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/bedrock/hello_world_completion_with_auth.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/bedrock/simple_agent_chat.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/chat_with_langfuse.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/data_processor.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/echo/debug_example.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/echo/prompt.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/echo/test.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/echo/video.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/field_extractor_example.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/multi_flow_example.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/openai/hello_world_chat.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/openai/hello_world_chat_with_telemetry.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/rag.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/time_utilities.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/vertex/hello_world_chat.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/vertex/hello_world_completion.qtype.yaml +0 -0
- {examples → qtype/examples}/legacy/vertex/hello_world_completion_with_auth.qtype.yaml +0 -0
- {examples → qtype/examples}/observability_debugging/trace_with_opentelemetry.qtype.yaml +0 -0
- {examples → qtype/examples}/research_assistant/research_assistant.qtype.yaml +0 -0
- {examples → qtype/examples}/research_assistant/tavily.oas.yaml +0 -0
- {examples → qtype/examples}/tutorials/01_hello_world.qtype.yaml +0 -0
- {examples → qtype/examples}/tutorials/02_conversational_chat.qtype.yaml +0 -0
- {qtype-0.1.12.dist-info → qtype-0.1.13.dist-info}/entry_points.txt +0 -0
- {qtype-0.1.12.dist-info → qtype-0.1.13.dist-info}/licenses/LICENSE +0 -0
qtype/interpreter/types.py
CHANGED
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
from typing import Any, Dict, Literal, Optional, Protocol, Union
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field, model_serializer
|
|
4
4
|
|
|
5
5
|
from qtype.base.types import StrictBaseModel
|
|
6
6
|
from qtype.dsl.domain_types import ChatMessage
|
|
7
7
|
from qtype.semantic.model import Step
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
class _UnsetType:
|
|
11
|
+
"""Sentinel representing an unset variable.
|
|
12
|
+
|
|
13
|
+
Distinguishes between:
|
|
14
|
+
- Variable never mentioned (not in dict)
|
|
15
|
+
- Variable explicitly unset (UNSET value in dict)
|
|
16
|
+
- Variable set to None (None value in dict)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_instance = None
|
|
20
|
+
|
|
21
|
+
def __new__(cls):
|
|
22
|
+
if cls._instance is None:
|
|
23
|
+
cls._instance = super().__new__(cls)
|
|
24
|
+
return cls._instance
|
|
25
|
+
|
|
26
|
+
def __repr__(self) -> str:
|
|
27
|
+
return "UNSET"
|
|
28
|
+
|
|
29
|
+
def __bool__(self) -> bool:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
UNSET = _UnsetType()
|
|
34
|
+
|
|
35
|
+
|
|
9
36
|
# Stream Event Types (Discriminated Union)
|
|
10
37
|
# These events are emitted by executors during flow execution
|
|
11
38
|
# and can be converted to Vercel UI chunks for frontend display
|
|
@@ -293,8 +320,9 @@ class FlowMessage(BaseModel):
|
|
|
293
320
|
"""
|
|
294
321
|
|
|
295
322
|
model_config = ConfigDict(
|
|
296
|
-
frozen=True
|
|
297
|
-
|
|
323
|
+
frozen=True,
|
|
324
|
+
arbitrary_types_allowed=True, # Allow UNSET sentinel
|
|
325
|
+
)
|
|
298
326
|
|
|
299
327
|
session: Session
|
|
300
328
|
variables: Dict[str, Any] = Field(
|
|
@@ -307,6 +335,49 @@ class FlowMessage(BaseModel):
|
|
|
307
335
|
"""Checks if this state has encountered an error."""
|
|
308
336
|
return self.error is not None
|
|
309
337
|
|
|
338
|
+
def is_set(self, var_id: str) -> bool:
|
|
339
|
+
"""Check if a variable is set (not UNSET, may be None)."""
|
|
340
|
+
value = self.variables.get(var_id, UNSET)
|
|
341
|
+
return value is not UNSET
|
|
342
|
+
|
|
343
|
+
def get_variable(self, var_id: str, *, default: Any = UNSET) -> Any:
|
|
344
|
+
"""Get variable value, raising if unset and no default provided.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
var_id: Variable identifier
|
|
348
|
+
default: Value to return if variable is unset. If not provided,
|
|
349
|
+
raises ValueError on unset variables.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Variable value (may be None if explicitly set to None)
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
ValueError: If variable is unset and no default provided
|
|
356
|
+
|
|
357
|
+
Examples:
|
|
358
|
+
# Required variable - throws if unset
|
|
359
|
+
value = message.get_variable("user_input")
|
|
360
|
+
|
|
361
|
+
# Optional variable - returns None if unset
|
|
362
|
+
value = message.get_variable("optional_field", default=None)
|
|
363
|
+
|
|
364
|
+
# Optional with custom default
|
|
365
|
+
value = message.get_variable("count", default=0)
|
|
366
|
+
"""
|
|
367
|
+
value = self.variables.get(var_id, UNSET)
|
|
368
|
+
|
|
369
|
+
if value is UNSET:
|
|
370
|
+
if default is UNSET:
|
|
371
|
+
raise ValueError(
|
|
372
|
+
(
|
|
373
|
+
f"Required variable '{var_id}' is not set. "
|
|
374
|
+
f"Available variables: {list(self.variables.keys())}"
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
return default
|
|
378
|
+
|
|
379
|
+
return value
|
|
380
|
+
|
|
310
381
|
def copy_with_error(self, step_id: str, exc: Exception) -> "FlowMessage":
|
|
311
382
|
"""Returns a copy of this state marked as failed."""
|
|
312
383
|
return self.model_copy(
|
|
@@ -319,15 +390,29 @@ class FlowMessage(BaseModel):
|
|
|
319
390
|
}
|
|
320
391
|
)
|
|
321
392
|
|
|
322
|
-
# It's useful to have copy-on-write style helpers
|
|
323
393
|
def copy_with_variables(
|
|
324
394
|
self, new_variables: dict[str, Any]
|
|
325
395
|
) -> "FlowMessage":
|
|
396
|
+
"""Create a new FlowMessage with updated variables.
|
|
397
|
+
|
|
398
|
+
Note: Can set variables to UNSET to explicitly mark them as unset.
|
|
399
|
+
"""
|
|
326
400
|
new_vars = self.variables.copy()
|
|
327
401
|
new_vars.update(new_variables)
|
|
328
402
|
new_state = self.model_copy(update={"variables": new_vars})
|
|
329
403
|
return new_state
|
|
330
404
|
|
|
405
|
+
@model_serializer
|
|
406
|
+
def serialize_model(self):
|
|
407
|
+
"""Custom serialization that excludes UNSET variables."""
|
|
408
|
+
return {
|
|
409
|
+
"session": self.session,
|
|
410
|
+
"variables": {
|
|
411
|
+
k: v for k, v in self.variables.items() if v is not UNSET
|
|
412
|
+
},
|
|
413
|
+
"error": self.error,
|
|
414
|
+
}
|
|
415
|
+
|
|
331
416
|
|
|
332
417
|
class InterpreterError(Exception):
|
|
333
418
|
"""Base exception class for ProtoGen interpreter errors."""
|
qtype/interpreter/typing.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import uuid
|
|
4
|
-
from typing import Any, Type
|
|
4
|
+
from typing import Any, Type
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, Field,
|
|
6
|
+
from pydantic import BaseModel, Field, create_model
|
|
7
7
|
|
|
8
8
|
from qtype.dsl.model import ListType, PrimitiveTypeEnum
|
|
9
9
|
from qtype.dsl.model import Variable as DSLVariable
|
|
@@ -138,37 +138,36 @@ def flow_results_to_output_container(
|
|
|
138
138
|
return output_container(outputs=outputs, errors=errors)
|
|
139
139
|
|
|
140
140
|
|
|
141
|
-
def
|
|
141
|
+
def convert_dict_to_typed_variables(
|
|
142
|
+
data: dict[str, Any], variables: list[Variable]
|
|
143
|
+
) -> dict[str, Any]:
|
|
142
144
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
Convert a dictionary of raw values to properly typed variables.
|
|
146
|
+
|
|
147
|
+
Uses Pydantic model validation to convert all values at once based on
|
|
148
|
+
Variable type declarations. This is more efficient than converting each
|
|
149
|
+
field individually.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
data: Dictionary with raw values (e.g., from DataFrame row)
|
|
153
|
+
variables: List of Variable definitions with type information
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Dictionary with values converted to their declared types
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ValidationError: If values cannot be converted to declared types
|
|
145
160
|
"""
|
|
146
|
-
|
|
161
|
+
# Create a Pydantic model from the variable definitions
|
|
162
|
+
model_class = create_model(
|
|
163
|
+
"TypedVariables",
|
|
164
|
+
__base__=BaseModel,
|
|
165
|
+
**_fields_from_variables(variables),
|
|
166
|
+
)
|
|
147
167
|
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
origin = get_origin(target_type)
|
|
168
|
+
# Validate and convert the data using Pydantic
|
|
169
|
+
validated = model_class.model_validate(data)
|
|
151
170
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return value
|
|
156
|
-
else:
|
|
157
|
-
# It's a generic (list[str], etc.).
|
|
158
|
-
# We skip the identity check and let TypeAdapter handle it.
|
|
159
|
-
pass
|
|
160
|
-
|
|
161
|
-
# 2. Handle Pydantic Models (Custom/Domain Types)
|
|
162
|
-
if hasattr(target_type, "model_validate"):
|
|
163
|
-
return target_type.model_validate(value) # type: ignore[misc]
|
|
164
|
-
|
|
165
|
-
# 3. Handle Primitives & Complex Python Types (List, Optional, Union)
|
|
166
|
-
try:
|
|
167
|
-
# TypeAdapter is the "V2 way" to validate things that aren't
|
|
168
|
-
# full Pydantic models (like List[int] or Optional[str])
|
|
169
|
-
return TypeAdapter(target_type).validate_python(value)
|
|
170
|
-
except Exception:
|
|
171
|
-
# Fallback to your original manual cast if TypeAdapter is overkill
|
|
172
|
-
if isinstance(target_type, type):
|
|
173
|
-
return target_type(value)
|
|
174
|
-
raise ValueError(f"Unsupported target type: {target_type}")
|
|
171
|
+
# Return as dict but preserve actual typed instances (not serialized)
|
|
172
|
+
# Use __dict__ to get the actual field values without serialization
|
|
173
|
+
return dict(validated)
|
qtype/mcp/server.py
CHANGED
|
@@ -2,11 +2,13 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import re
|
|
5
|
+
import tempfile
|
|
5
6
|
from functools import lru_cache
|
|
6
7
|
from importlib.resources import files
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
11
|
+
import tantivy
|
|
10
12
|
from mcp.server.fastmcp import FastMCP
|
|
11
13
|
from pydantic import BaseModel
|
|
12
14
|
|
|
@@ -20,26 +22,128 @@ SNIPPET_REGEX = re.compile(r'--8<--\s+"([^"]+)"')
|
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
# ============================================================================
|
|
23
|
-
#
|
|
25
|
+
# Resource Abstraction Layer
|
|
24
26
|
# ============================================================================
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
"""
|
|
29
|
+
class ResourceDirectory:
|
|
30
|
+
"""Abstraction for accessing resource directories (docs, examples, etc.)."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self, name: str, file_extension: str, resolve_snippets: bool = False
|
|
34
|
+
):
|
|
35
|
+
"""Initialize a resource directory.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
name: Directory name (e.g., "docs", "examples")
|
|
39
|
+
file_extension: File extension to search for (e.g., ".md", ".yaml")
|
|
40
|
+
resolve_snippets: Whether to resolve MkDocs snippets in file content
|
|
41
|
+
"""
|
|
42
|
+
self.name = name
|
|
43
|
+
self.file_extension = file_extension
|
|
44
|
+
self.resolve_snippets = resolve_snippets
|
|
45
|
+
self._path_cache: Path | None = None
|
|
46
|
+
|
|
47
|
+
def get_path(self) -> Path:
|
|
48
|
+
"""Get the path to this resource directory.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Path to the resource directory, trying installed package first,
|
|
52
|
+
then falling back to development path.
|
|
53
|
+
"""
|
|
54
|
+
if self._path_cache is not None:
|
|
55
|
+
return self._path_cache
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Try to get from installed package
|
|
59
|
+
resource_root = files("qtype") / self.name
|
|
60
|
+
# Check if it exists by trying to iterate
|
|
61
|
+
list(resource_root.iterdir())
|
|
62
|
+
self._path_cache = Path(str(resource_root))
|
|
63
|
+
except (FileNotFoundError, AttributeError, TypeError):
|
|
64
|
+
# Fall back to development path
|
|
65
|
+
self._path_cache = Path(__file__).parent.parent.parent / self.name
|
|
66
|
+
|
|
67
|
+
return self._path_cache
|
|
68
|
+
|
|
69
|
+
def get_file(self, file_path: str) -> str:
|
|
70
|
+
"""Get the content of a specific file.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
file_path: Relative path to the file from the resource root.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The full content of the file.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
FileNotFoundError: If the specified file doesn't exist.
|
|
80
|
+
ValueError: If the path tries to access files outside the directory.
|
|
81
|
+
"""
|
|
82
|
+
resource_path = self.get_path()
|
|
83
|
+
|
|
84
|
+
# Resolve the requested file path
|
|
85
|
+
requested_file = (resource_path / file_path).resolve()
|
|
86
|
+
|
|
87
|
+
# Security check: ensure the resolved path is within resource directory
|
|
88
|
+
try:
|
|
89
|
+
requested_file.relative_to(resource_path.resolve())
|
|
90
|
+
except ValueError:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Invalid path: '{file_path}' is outside {self.name} directory"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if not requested_file.exists():
|
|
96
|
+
raise FileNotFoundError(
|
|
97
|
+
f"{self.name.capitalize()} file not found: '{file_path}'. "
|
|
98
|
+
f"Use list_{self.name} to see available files."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if not requested_file.is_file():
|
|
102
|
+
raise ValueError(f"Path is not a file: '{file_path}'")
|
|
103
|
+
|
|
104
|
+
content = requested_file.read_text(encoding="utf-8")
|
|
105
|
+
|
|
106
|
+
# Apply snippet resolution if enabled
|
|
107
|
+
if self.resolve_snippets:
|
|
108
|
+
content = _resolve_snippets(content, requested_file)
|
|
109
|
+
|
|
110
|
+
return content
|
|
111
|
+
|
|
112
|
+
def list_files(self) -> list[str]:
|
|
113
|
+
"""List all files in this resource directory.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Sorted list of relative paths to all files with the configured extension.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
FileNotFoundError: If the resource directory doesn't exist.
|
|
120
|
+
"""
|
|
121
|
+
resource_path = self.get_path()
|
|
122
|
+
|
|
123
|
+
if not resource_path.exists():
|
|
124
|
+
raise FileNotFoundError(
|
|
125
|
+
f"{self.name.capitalize()} directory not found: {resource_path}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Find all files with the configured extension
|
|
129
|
+
pattern = f"*{self.file_extension}"
|
|
130
|
+
files_list = []
|
|
131
|
+
for file in resource_path.rglob(pattern):
|
|
132
|
+
# Get relative path from resource root
|
|
133
|
+
rel_path = file.relative_to(resource_path)
|
|
134
|
+
files_list.append(str(rel_path))
|
|
135
|
+
|
|
136
|
+
return sorted(files_list)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Initialize resource directories
|
|
140
|
+
_docs_resource = ResourceDirectory("docs", ".md", resolve_snippets=True)
|
|
141
|
+
_examples_resource = ResourceDirectory("examples", ".yaml")
|
|
29
142
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
# Try to get from installed package
|
|
36
|
-
docs_root = files("qtype") / "docs"
|
|
37
|
-
# Check if it exists by trying to iterate
|
|
38
|
-
list(docs_root.iterdir())
|
|
39
|
-
return Path(str(docs_root))
|
|
40
|
-
except (FileNotFoundError, AttributeError, TypeError):
|
|
41
|
-
# Fall back to development path
|
|
42
|
-
return Path(__file__).parent.parent.parent / "docs"
|
|
143
|
+
|
|
144
|
+
# ============================================================================
|
|
145
|
+
# Helper Functions
|
|
146
|
+
# ============================================================================
|
|
43
147
|
|
|
44
148
|
|
|
45
149
|
@lru_cache(maxsize=1)
|
|
@@ -55,7 +159,7 @@ def _load_schema() -> dict[str, Any]:
|
|
|
55
159
|
"""
|
|
56
160
|
# Try to load from installed package data first
|
|
57
161
|
try:
|
|
58
|
-
schema_file = files("qtype") / "qtype.schema.json"
|
|
162
|
+
schema_file = files("qtype") / "schema" / "qtype.schema.json"
|
|
59
163
|
schema_text = schema_file.read_text(encoding="utf-8")
|
|
60
164
|
return json.loads(schema_text)
|
|
61
165
|
except (FileNotFoundError, AttributeError):
|
|
@@ -67,7 +171,7 @@ def _load_schema() -> dict[str, Any]:
|
|
|
67
171
|
return json.load(f)
|
|
68
172
|
|
|
69
173
|
|
|
70
|
-
def
|
|
174
|
+
def _resolve_snippets(content: str, base_path: Path) -> str:
|
|
71
175
|
"""
|
|
72
176
|
Recursively finds and replaces MkDocs snippets in markdown content.
|
|
73
177
|
Mimics the behavior of pymdownx.snippets.
|
|
@@ -76,7 +180,7 @@ def resolve_snippets(content: str, base_path: Path) -> str:
|
|
|
76
180
|
content: The markdown content to process
|
|
77
181
|
base_path: Path to the file being processed (used to resolve relative paths)
|
|
78
182
|
"""
|
|
79
|
-
docs_root =
|
|
183
|
+
docs_root = _docs_resource.get_path()
|
|
80
184
|
project_root = docs_root.parent
|
|
81
185
|
|
|
82
186
|
def replace_match(match):
|
|
@@ -92,7 +196,7 @@ def resolve_snippets(content: str, base_path: Path) -> str:
|
|
|
92
196
|
for candidate in candidates:
|
|
93
197
|
if candidate.exists() and candidate.is_file():
|
|
94
198
|
# Recursively resolve snippets inside the included file
|
|
95
|
-
return
|
|
199
|
+
return _resolve_snippets(
|
|
96
200
|
candidate.read_text(encoding="utf-8"), candidate
|
|
97
201
|
)
|
|
98
202
|
|
|
@@ -101,6 +205,84 @@ def resolve_snippets(content: str, base_path: Path) -> str:
|
|
|
101
205
|
return SNIPPET_REGEX.sub(replace_match, content)
|
|
102
206
|
|
|
103
207
|
|
|
208
|
+
@lru_cache(maxsize=1)
|
|
209
|
+
def _build_search_index() -> tantivy.Index:
|
|
210
|
+
"""Build and cache a Tantivy search index for docs and examples.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
A Tantivy Index containing all documentation markdown files
|
|
214
|
+
and example YAML files.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
Exception: If index building fails.
|
|
218
|
+
"""
|
|
219
|
+
docs_path = _docs_resource.get_path()
|
|
220
|
+
examples_path = _examples_resource.get_path()
|
|
221
|
+
|
|
222
|
+
# Create schema with fields for title, path, and content
|
|
223
|
+
schema_builder = tantivy.SchemaBuilder()
|
|
224
|
+
schema_builder.add_text_field("title", stored=True)
|
|
225
|
+
schema_builder.add_text_field("path", stored=True)
|
|
226
|
+
schema_builder.add_text_field("content", stored=True)
|
|
227
|
+
schema_builder.add_text_field("type", stored=True)
|
|
228
|
+
schema = schema_builder.build()
|
|
229
|
+
|
|
230
|
+
# Create index in temporary directory
|
|
231
|
+
index = tantivy.Index(schema, path=tempfile.mkdtemp())
|
|
232
|
+
writer = index.writer()
|
|
233
|
+
|
|
234
|
+
# Helper to index files
|
|
235
|
+
def index_files(
|
|
236
|
+
root_path: Path,
|
|
237
|
+
pattern: str,
|
|
238
|
+
type_label: str,
|
|
239
|
+
path_prefix: str,
|
|
240
|
+
process_content=None,
|
|
241
|
+
extract_title=None,
|
|
242
|
+
):
|
|
243
|
+
for file_path in root_path.rglob(pattern):
|
|
244
|
+
content = file_path.read_text(encoding="utf-8")
|
|
245
|
+
if process_content:
|
|
246
|
+
content = process_content(content, file_path)
|
|
247
|
+
|
|
248
|
+
rel_path = str(file_path.relative_to(root_path))
|
|
249
|
+
title = (
|
|
250
|
+
extract_title(content, file_path)
|
|
251
|
+
if extract_title
|
|
252
|
+
else file_path.stem
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
writer.add_document(
|
|
256
|
+
tantivy.Document(
|
|
257
|
+
title=title,
|
|
258
|
+
path=f"{path_prefix}/{rel_path}",
|
|
259
|
+
content=content,
|
|
260
|
+
type=type_label,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Extract title from markdown first heading
|
|
265
|
+
def extract_md_title(content: str, file_path: Path) -> str:
|
|
266
|
+
for line in content.split("\n"):
|
|
267
|
+
if line.startswith("# "):
|
|
268
|
+
return line[2:].strip()
|
|
269
|
+
return file_path.stem
|
|
270
|
+
|
|
271
|
+
# Index documentation and examples
|
|
272
|
+
index_files(
|
|
273
|
+
docs_path,
|
|
274
|
+
"*.md",
|
|
275
|
+
"documentation",
|
|
276
|
+
"docs",
|
|
277
|
+
process_content=_resolve_snippets,
|
|
278
|
+
extract_title=extract_md_title,
|
|
279
|
+
)
|
|
280
|
+
index_files(examples_path, "*.yaml", "example", "examples")
|
|
281
|
+
|
|
282
|
+
writer.commit()
|
|
283
|
+
return index
|
|
284
|
+
|
|
285
|
+
|
|
104
286
|
# ============================================================================
|
|
105
287
|
# Tool Functions
|
|
106
288
|
# ============================================================================
|
|
@@ -122,6 +304,10 @@ class MermaidVisualizationResult(BaseModel):
|
|
|
122
304
|
preview_instructions: str
|
|
123
305
|
|
|
124
306
|
|
|
307
|
+
# Rebuild model after nested dependency is defined
|
|
308
|
+
MermaidVisualizationResult.model_rebuild()
|
|
309
|
+
|
|
310
|
+
|
|
125
311
|
@mcp.tool(
|
|
126
312
|
title="Convert API Specification to QType Tools",
|
|
127
313
|
description=(
|
|
@@ -287,36 +473,13 @@ def get_documentation(file_path: str) -> str:
|
|
|
287
473
|
Use list_documentation to see all available files.
|
|
288
474
|
|
|
289
475
|
Returns:
|
|
290
|
-
The full markdown content of the documentation file.
|
|
476
|
+
The full markdown content of the documentation file with snippets resolved.
|
|
291
477
|
|
|
292
478
|
Raises:
|
|
293
479
|
FileNotFoundError: If the specified file doesn't exist.
|
|
294
480
|
ValueError: If the path tries to access files outside the docs directory.
|
|
295
481
|
"""
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# Resolve the requested file path
|
|
299
|
-
requested_file = (docs_path / file_path).resolve()
|
|
300
|
-
|
|
301
|
-
# Security check: ensure the resolved path is within docs directory
|
|
302
|
-
try:
|
|
303
|
-
requested_file.relative_to(docs_path.resolve())
|
|
304
|
-
except ValueError:
|
|
305
|
-
raise ValueError(
|
|
306
|
-
f"Invalid path: '{file_path}' is outside documentation directory"
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
if not requested_file.exists():
|
|
310
|
-
raise FileNotFoundError(
|
|
311
|
-
f"Documentation file not found: '{file_path}'. "
|
|
312
|
-
"Use list_documentation to see available files."
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
if not requested_file.is_file():
|
|
316
|
-
raise ValueError(f"Path is not a file: '{file_path}'")
|
|
317
|
-
|
|
318
|
-
content = requested_file.read_text(encoding="utf-8")
|
|
319
|
-
return resolve_snippets(content, requested_file)
|
|
482
|
+
return _docs_resource.get_file(file_path)
|
|
320
483
|
|
|
321
484
|
|
|
322
485
|
@mcp.tool(
|
|
@@ -363,21 +526,113 @@ def list_documentation() -> list[str]:
|
|
|
363
526
|
Paths are relative to the docs root (e.g., "components/Flow.md",
|
|
364
527
|
"Tutorials/getting_started.md").
|
|
365
528
|
"""
|
|
366
|
-
|
|
529
|
+
return _docs_resource.list_files()
|
|
367
530
|
|
|
368
|
-
if not docs_path.exists():
|
|
369
|
-
raise FileNotFoundError(
|
|
370
|
-
f"Documentation directory not found: {docs_path}"
|
|
371
|
-
)
|
|
372
531
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
532
|
+
@mcp.tool(
|
|
533
|
+
title="Get QType Example",
|
|
534
|
+
description=(
|
|
535
|
+
"Returns the content of a specific example YAML file. "
|
|
536
|
+
"Use list_examples first to see available files. "
|
|
537
|
+
"Provide the relative path (e.g., 'conversational_ai/simple_chatbot.qtype.yaml', "
|
|
538
|
+
"'data_processing/csv_processor.qtype.yaml')."
|
|
539
|
+
),
|
|
540
|
+
)
|
|
541
|
+
def get_example(file_path: str) -> str:
|
|
542
|
+
"""Get the content of a specific example file.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
file_path: Relative path to the example file from the examples root.
|
|
546
|
+
Example: "conversational_ai/simple_chatbot.qtype.yaml",
|
|
547
|
+
"data_processing/csv_processor.qtype.yaml".
|
|
548
|
+
Use list_examples to see all available files.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
The full YAML content of the example file.
|
|
552
|
+
|
|
553
|
+
Raises:
|
|
554
|
+
FileNotFoundError: If the specified file doesn't exist.
|
|
555
|
+
ValueError: If the path tries to access files outside the examples directory.
|
|
556
|
+
"""
|
|
557
|
+
return _examples_resource.get_file(file_path)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
@mcp.tool(
|
|
561
|
+
title="List QType Examples",
|
|
562
|
+
description=(
|
|
563
|
+
"Returns a list of all available example YAML files. "
|
|
564
|
+
"Use this to discover what examples exist, then retrieve "
|
|
565
|
+
"specific files with get_example. Examples are organized by category: "
|
|
566
|
+
"conversational_ai/ (chatbots and agents), data_processing/ (ETL and transformations), "
|
|
567
|
+
"invoke_models/ (LLM usage), language_features/ (QType syntax examples), etc."
|
|
568
|
+
),
|
|
569
|
+
structured_output=True,
|
|
570
|
+
)
|
|
571
|
+
def list_examples() -> list[str]:
|
|
572
|
+
"""List all available example YAML files.
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
Sorted list of relative paths to all .yaml example files.
|
|
576
|
+
Paths are relative to the examples root (e.g.,
|
|
577
|
+
"conversational_ai/simple_chatbot.qtype.yaml",
|
|
578
|
+
"data_processing/csv_processor.qtype.yaml").
|
|
579
|
+
"""
|
|
580
|
+
return _examples_resource.list_files()
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
@mcp.tool(
|
|
584
|
+
title="Search QType Library",
|
|
585
|
+
description=(
|
|
586
|
+
"Full-text search across all QType documentation and examples. "
|
|
587
|
+
"Returns matching documents and example YAML files ranked by relevance. "
|
|
588
|
+
"Use this to find documentation about specific topics, features, or components, "
|
|
589
|
+
"or to discover example implementations. Doc paths can be used with get_documentation."
|
|
590
|
+
),
|
|
591
|
+
structured_output=True,
|
|
592
|
+
)
|
|
593
|
+
def search_library(query: str, limit: int = 10) -> list[dict[str, Any]]:
|
|
594
|
+
"""Search library using full-text search.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
query: Search query string. Can include multiple words, phrases,
|
|
598
|
+
or boolean operators (AND, OR, NOT).
|
|
599
|
+
limit: Maximum number of results to return (default: 10, max: 50).
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
List of matching items with:
|
|
603
|
+
- title: Item title
|
|
604
|
+
- path: Relative path (docs/ or examples/ prefix)
|
|
605
|
+
- type: Either "documentation" or "example"
|
|
606
|
+
- score: Relevance score (higher is more relevant)
|
|
607
|
+
|
|
608
|
+
Examples:
|
|
609
|
+
search_library("flow execution") # Find docs/examples about flows
|
|
610
|
+
search_library("DocumentSource") # Find component docs
|
|
611
|
+
search_library("authentication AND API") # Boolean search
|
|
612
|
+
"""
|
|
613
|
+
# Clamp limit to reasonable range
|
|
614
|
+
limit = max(1, min(limit, 50))
|
|
615
|
+
|
|
616
|
+
index = _build_search_index()
|
|
617
|
+
index.reload()
|
|
618
|
+
searcher = index.searcher()
|
|
619
|
+
tantivy_query = index.parse_query(query, ["title", "content"])
|
|
620
|
+
|
|
621
|
+
search_results = searcher.search(tantivy_query, limit)
|
|
622
|
+
|
|
623
|
+
results = []
|
|
624
|
+
for score, doc_address in search_results.hits:
|
|
625
|
+
doc = searcher.doc(doc_address)
|
|
626
|
+
results.append(
|
|
627
|
+
{
|
|
628
|
+
"title": doc["title"][0],
|
|
629
|
+
"path": doc["path"][0],
|
|
630
|
+
"type": doc["type"][0],
|
|
631
|
+
"score": score,
|
|
632
|
+
}
|
|
633
|
+
)
|
|
379
634
|
|
|
380
|
-
return
|
|
635
|
+
return results
|
|
381
636
|
|
|
382
637
|
|
|
383
638
|
@mcp.tool(
|