fastapi-authly 0.1.34__tar.gz → 0.1.35__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/PKG-INFO +1 -1
  2. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/pyproject.toml +1 -1
  3. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/__about__.py +1 -1
  4. fastapi_authly-0.1.35/src/fastapi_authly/ai/output_parsers/__init__.py +4 -0
  5. fastapi_authly-0.1.35/src/fastapi_authly/ai/output_parsers/direct.py +199 -0
  6. fastapi_authly-0.1.34/ScienceClaw-master.zip +0 -0
  7. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/.gitignore +0 -0
  8. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/LICENSE +0 -0
  9. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/README.md +0 -0
  10. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/README.zh.md +0 -0
  11. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/docs//345/207/275/346/225/260/345/274/217/347/274/226/347/250/213/344/270/203/346/255/246/345/231/250.md" +0 -0
  12. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/examples/chart_tools.py +0 -0
  13. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/examples/correct_usage.py +0 -0
  14. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/__init__.py +0 -0
  15. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/ai/__init__.py +0 -0
  16. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/ai/embeddings.py +0 -0
  17. /fastapi_authly-0.1.34/src/fastapi_authly/ai/output_parsers.py → /fastapi_authly-0.1.35/src/fastapi_authly/ai/output_parsers/tool.py +0 -0
  18. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/ai/rerank.py +0 -0
  19. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/ai/vectorstores.py +0 -0
  20. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/auth.py +0 -0
  21. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/charts.py +0 -0
  22. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/contrib/__init__.py +0 -0
  23. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/contrib/tortoise_pg.py +0 -0
  24. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/__init__.py +0 -0
  25. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/config/__init__.py +0 -0
  26. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/config/auth.py +0 -0
  27. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/config/base.py +0 -0
  28. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/config/database.py +0 -0
  29. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/error_codes.py +0 -0
  30. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/exception_handlers.py +0 -0
  31. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/exceptions.py +0 -0
  32. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/logging.py +0 -0
  33. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/pagination.py +0 -0
  34. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/response.py +0 -0
  35. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/core/security.py +0 -0
  36. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/deps.py +0 -0
  37. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/docs.py +0 -0
  38. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/interfaces.py +0 -0
  39. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/llm/__init__.py +0 -0
  40. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/llm/config.py +0 -0
  41. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/llm/exceptions.py +0 -0
  42. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/llm/factory.py +0 -0
  43. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/llm/providers.py +0 -0
  44. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/models/__init__.py +0 -0
  45. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/models/user.py +0 -0
  46. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/repo.py +0 -0
  47. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/schedule/__init__.py +0 -0
  48. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/schedule/config.py +0 -0
  49. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/schedule/logging.py +0 -0
  50. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/schedule/manager.py +0 -0
  51. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/schemas/__init__.py +0 -0
  52. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/schemas/user.py +0 -0
  53. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/skill_runtime/__init__.py +0 -0
  54. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/skill_runtime/langchain_adapter.py +0 -0
  55. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/skill_runtime/models.py +0 -0
  56. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/skill_runtime/registry.py +0 -0
  57. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/skill_runtime/runtime.py +0 -0
  58. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/static/echarts.min.js +0 -0
  59. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/static/favicon.svg +0 -0
  60. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/static/scalar/standalone.js +0 -0
  61. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/src/fastapi_authly/static/scalar/style.css +0 -0
  62. {fastapi_authly-0.1.34 → fastapi_authly-0.1.35}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapi-authly
3
- Version: 0.1.34
3
+ Version: 0.1.35
4
4
  Summary: A modular authentication system for FastAPI with OAuth2, JWT, and password recovery
5
5
  Project-URL: Homepage, https://github.com/yourusername/fastapi-auth-module
6
6
  Project-URL: Documentation, https://yourusername.github.io/fastapi-auth-module/
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fastapi-authly"
7
- version = "0.1.34"
7
+ version = "0.1.35"
8
8
  description = "A modular authentication system for FastAPI with OAuth2, JWT, and password recovery"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,3 +1,3 @@
1
1
  """Version information."""
2
2
 
3
- __version__ = "0.1.34"
3
+ __version__ = "0.1.35"
@@ -0,0 +1,4 @@
1
+ from .tool import StructuredAgent
2
+ from .direct import JsonExtractor
3
+
4
+ __all__ = ["StructuredAgent", "JsonExtractor"]
@@ -0,0 +1,199 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
5
+ from collections.abc import Callable
6
+ from typing import Any, Generic, TypeVar
7
+
8
+ from json_repair import repair_json
9
+ from langchain_core.language_models import BaseChatModel
10
+ from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, convert_to_openai_messages
11
+ from pydantic import BaseModel
12
+
13
+ logger = logging.getLogger(__name__)
14
+ T = TypeVar("T", bound=BaseModel)
15
+ ResultValidator = Callable[[T, str], None]
16
+
17
+
18
+ def jsonify(value: str | BaseMessage) -> Any:
19
+ """提取文本中的 JSON;标准解析失败时尝试修复常见格式错误。"""
20
+ content = value.content if isinstance(value, BaseMessage) else value
21
+ if isinstance(content, list):
22
+ content = "\n".join(
23
+ block if isinstance(block, str) else block.get("text", "")
24
+ for block in content
25
+ if isinstance(block, str) or isinstance(block, dict) and block.get("type") == "text"
26
+ )
27
+ if not isinstance(content, str) or not content.strip():
28
+ raise ValueError("模型最终回复没有可解析的 JSON 文本")
29
+ text = content.strip()
30
+ try:
31
+ return json.loads(text)
32
+ except json.JSONDecodeError:
33
+ try:
34
+ repaired = repair_json(text, return_objects=True)
35
+ except (ValueError, TypeError) as exc:
36
+ raise ValueError(f"JSON 修复失败:{exc}") from exc
37
+ if repaired == "":
38
+ raise ValueError("无法从模型最终回复中提取 JSON,请输出完整的 JSON")
39
+ return repaired
40
+
41
+
42
+ class JsonExtractor(Generic[T]):
43
+ """直接调用聊天模型,从回复文本中提取经过 Pydantic 校验的 JSON。
44
+
45
+ 将 JSON Schema 写入系统提示词,不启用模型原生 JSON 模式。
46
+ JSON 解析、Pydantic 或业务校验抛出 ValueError 时,保留对话历史并
47
+ 追加错误反馈重试。max_validation_attempts 包含首次调用;模型调用
48
+ 本身的异常直接向外抛出。
49
+
50
+ Examples:
51
+ model 由调用方提供,为 BaseChatModel 实例,无需支持工具调用。
52
+
53
+ >>> from pydantic import BaseModel, Field
54
+ >>> class ComplaintResult(BaseModel):
55
+ ... person_name: str | None = Field(description="被投诉人姓名,未提及则为 null")
56
+ ... unit_name: str | None = Field(description="被投诉单位,未提及则为 null")
57
+ >>> extractor = JsonExtractor(
58
+ ... model,
59
+ ... ComplaintResult,
60
+ ... system_prompt="从诉求正文中抽取被投诉人和被投诉单位,不要编造信息。",
61
+ ... max_validation_attempts=3,
62
+ ... )
63
+ >>> result = extractor.invoke("投诉光明派出所的张三处理不当。")
64
+ >>> isinstance(result, ComplaintResult)
65
+ True
66
+ >>> data = result.model_dump()
67
+
68
+ 在异步函数中调用:
69
+
70
+ >>> result = await extractor.ainvoke("投诉光明派出所的张三处理不当。")
71
+
72
+ 如需校验抽取值来自原文,可提供业务校验函数;失败时抛出 ValueError:
73
+
74
+ >>> def validate_source(result: ComplaintResult, source_text: str) -> None:
75
+ ... for name in (result.person_name, result.unit_name):
76
+ ... if name is not None and name not in source_text:
77
+ ... raise ValueError(f"{name!r} 未出现在原文,请使用原文名称或 null")
78
+ >>> checked = JsonExtractor(
79
+ ... model, ComplaintResult,
80
+ ... system_prompt="抽取被投诉人和被投诉单位。",
81
+ ... result_validator=validate_source,
82
+ ... )
83
+ >>> content = "投诉光明派出所的张三处理不当。"
84
+ >>> result = checked.invoke(content, source_text=content)
85
+
86
+ 调试时同时启用 debug_model 和本模块的 DEBUG 日志:
87
+
88
+ >>> import logging
89
+ >>> logging.basicConfig(level=logging.INFO)
90
+ >>> logging.getLogger(JsonExtractor.__module__).setLevel(logging.DEBUG)
91
+ >>> extractor = JsonExtractor(
92
+ ... model, ComplaintResult, system_prompt="抽取投诉信息。", debug_model=True,
93
+ ... )
94
+ """
95
+
96
+ def __init__(
97
+ self,
98
+ model: BaseChatModel,
99
+ schema: type[T],
100
+ *,
101
+ system_prompt: str,
102
+ max_validation_attempts: int = 3,
103
+ result_validator: ResultValidator[T] | None = None,
104
+ debug_model: bool = False,
105
+ ) -> None:
106
+ self.model = model
107
+ self.schema = schema
108
+ self.system_prompt = system_prompt
109
+ self.max_validation_attempts = self._positive(max_validation_attempts, "max_validation_attempts")
110
+ self.result_validator = result_validator
111
+ self.debug_model = debug_model
112
+
113
+ @staticmethod
114
+ def _positive(value: int, name: str) -> int:
115
+ if not isinstance(value, int) or isinstance(value, bool) or value < 1:
116
+ raise ValueError(f"{name} 必须是大于 0 的整数")
117
+ return value
118
+
119
+ def _messages(self, message: str) -> list[BaseMessage]:
120
+ schema_json = json.dumps(self.schema.model_json_schema(), ensure_ascii=False, indent=2)
121
+ prompt = (
122
+ f"{self.system_prompt}\n\n"
123
+ "最终回复必须只包含一个符合下述 JSON Schema 的完整 JSON 值。\n"
124
+ "不要输出 Markdown 代码围栏、解释或推理过程。字段类型、必填项、枚举值及约束"
125
+ "必须遵守 Schema;只有 Schema 允许时才能使用 null。\n"
126
+ "如果收到校验错误,请结合已有对话修正,重新输出完整 JSON。\n"
127
+ f"JSON Schema:\n{schema_json}"
128
+ )
129
+ return [SystemMessage(content=prompt), HumanMessage(content=message)]
130
+
131
+ def _prepare(self, max_validation_attempts: int | None, source_text: str | None) -> int:
132
+ attempts = (
133
+ self.max_validation_attempts if max_validation_attempts is None
134
+ else self._positive(max_validation_attempts, "max_validation_attempts")
135
+ )
136
+ if self.result_validator is not None and not isinstance(source_text, str):
137
+ raise ValueError("设置 result_validator 时必须传入可信原文 source_text")
138
+ return attempts
139
+
140
+ def _result(self, response: BaseMessage, source_text: str | None) -> T:
141
+ result = self.schema.model_validate(jsonify(response))
142
+ if self.result_validator is not None:
143
+ assert source_text is not None
144
+ self.result_validator(result, source_text)
145
+ return result
146
+
147
+ def _validate_or_feedback(
148
+ self, messages: list[BaseMessage], response: BaseMessage,
149
+ source_text: str | None, attempt: int, attempts: int,
150
+ ) -> T | None:
151
+ messages.append(response)
152
+ if self.debug_model and logger.isEnabledFor(logging.DEBUG):
153
+ logger.debug("模型返回:\n%s", response.pretty_repr())
154
+ try:
155
+ return self._result(response, source_text)
156
+ except ValueError as exc:
157
+ logger.warning("JSON 结果校验未通过(第 %d/%d 次):%s", attempt, attempts, exc)
158
+ if attempt == attempts:
159
+ raise ValueError(f"结果在 {attempts} 次调用后仍未通过校验:{exc}") from exc
160
+ messages.append(HumanMessage(content=(
161
+ f"上一轮最终结果未通过校验,错误信息如下:\n{exc}\n"
162
+ "请根据原始任务、已有对话和 JSON Schema 修正,重新输出完整 JSON。"
163
+ )))
164
+ return None
165
+
166
+ def _log_request(self, messages: list[BaseMessage], attempt: int) -> None:
167
+ if self.debug_model and logger.isEnabledFor(logging.DEBUG):
168
+ logger.debug(
169
+ "第 %d 轮模型调用,请求:\n%s", attempt,
170
+ json.dumps(convert_to_openai_messages(messages), ensure_ascii=False, indent=2, default=str),
171
+ )
172
+
173
+ def invoke(
174
+ self, message: str, *, source_text: str | None = None,
175
+ max_validation_attempts: int | None = None,
176
+ ) -> T:
177
+ attempts = self._prepare(max_validation_attempts, source_text)
178
+ messages = self._messages(message)
179
+ for attempt in range(1, attempts + 1):
180
+ self._log_request(messages, attempt)
181
+ response = self.model.invoke(messages)
182
+ result = self._validate_or_feedback(messages, response, source_text, attempt, attempts)
183
+ if result is not None:
184
+ return result
185
+ raise RuntimeError("未获得 JSON 结果")
186
+
187
+ async def ainvoke(
188
+ self, message: str, *, source_text: str | None = None,
189
+ max_validation_attempts: int | None = None,
190
+ ) -> T:
191
+ attempts = self._prepare(max_validation_attempts, source_text)
192
+ messages = self._messages(message)
193
+ for attempt in range(1, attempts + 1):
194
+ self._log_request(messages, attempt)
195
+ response = await self.model.ainvoke(messages)
196
+ result = self._validate_or_feedback(messages, response, source_text, attempt, attempts)
197
+ if result is not None:
198
+ return result
199
+ raise RuntimeError("未获得 JSON 结果")
File without changes
File without changes