Skip to content
Merged
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
5 changes: 5 additions & 0 deletions crates/ov_cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ impl HttpClient {
include: Option<String>,
exclude: Option<String>,
directly_upload_media: bool,
watch_interval: f64,
) -> Result<serde_json::Value> {
let path_obj = Path::new(path);

Expand All @@ -501,6 +502,7 @@ impl HttpClient {
"include": include,
"exclude": exclude,
"directly_upload_media": directly_upload_media,
"watch_interval": watch_interval,
});

self.post("/api/v1/resources", &body).await
Expand All @@ -520,6 +522,7 @@ impl HttpClient {
"include": include,
"exclude": exclude,
"directly_upload_media": directly_upload_media,
"watch_interval": watch_interval,
});

self.post("/api/v1/resources", &body).await
Expand All @@ -537,6 +540,7 @@ impl HttpClient {
"include": include,
"exclude": exclude,
"directly_upload_media": directly_upload_media,
"watch_interval": watch_interval,
});

self.post("/api/v1/resources", &body).await
Expand All @@ -555,6 +559,7 @@ impl HttpClient {
"include": include,
"exclude": exclude,
"directly_upload_media": directly_upload_media,
"watch_interval": watch_interval,
});

self.post("/api/v1/resources", &body).await
Expand Down
2 changes: 2 additions & 0 deletions crates/ov_cli/src/commands/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub async fn add_resource(
include: Option<String>,
exclude: Option<String>,
directly_upload_media: bool,
watch_interval: f64,
format: OutputFormat,
compact: bool,
) -> Result<()> {
Expand All @@ -33,6 +34,7 @@ pub async fn add_resource(
include,
exclude,
directly_upload_media,
watch_interval,
)
.await?;
output_success(&result, format, compact);
Expand Down
7 changes: 7 additions & 0 deletions crates/ov_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ enum Commands {
/// Do not directly upload media files
#[arg(long = "no-directly-upload-media", default_value_t = false)]
no_directly_upload_media: bool,
/// Watch interval in minutes for automatic resource monitoring (0 = no monitoring)
#[arg(long, default_value = "0")]
watch_interval: f64,
},
/// Add a skill into OpenViking
AddSkill {
Expand Down Expand Up @@ -525,6 +528,7 @@ async fn main() {
include,
exclude,
no_directly_upload_media,
watch_interval,
} => {
handle_add_resource(
path,
Expand All @@ -539,6 +543,7 @@ async fn main() {
include,
exclude,
no_directly_upload_media,
watch_interval,
ctx,
)
.await
Expand Down Expand Up @@ -655,6 +660,7 @@ async fn handle_add_resource(
include: Option<String>,
exclude: Option<String>,
no_directly_upload_media: bool,
watch_interval: f64,
ctx: CliContext,
) -> Result<()> {
let is_url = path.starts_with("http://")
Expand Down Expand Up @@ -722,6 +728,7 @@ async fn handle_add_resource(
include,
exclude,
directly_upload_media,
watch_interval,
ctx.output_format,
ctx.compact,
).await
Expand Down
43 changes: 43 additions & 0 deletions docs/en/api/02-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Add a resource to the knowledge base.
| instruction | str | No | "" | Special processing instructions |
| wait | bool | No | False | Wait for semantic processing to complete |
| timeout | float | No | None | Timeout in seconds (only used when wait=True) |
| watch_interval | float | No | 0 | Watch interval (minutes). >0 enables/updates watch; <=0 disables watch. Only takes effect when target is provided |

**Python SDK (Embedded / HTTP)**

Expand Down Expand Up @@ -168,6 +169,48 @@ curl -X POST http://localhost:1933/api/v1/system/wait \
openviking add-resource ./documents/guide.md --wait
```

**Example: Watch for Updates (watch_interval)**

`watch_interval` is in minutes and periodically triggers re-processing for the specified target URI:

- `watch_interval > 0`: create (or reactivate and update) a watch task for the `target`
- `watch_interval <= 0`: disable (deactivate) the watch task for the `target`
- watch tasks are only managed when `target` / CLI `--to` is provided

If there is already an active watch task for the same `target`, submitting another request with `watch_interval > 0` returns a conflict error. Disable it first (`watch_interval = 0`) and then set a new interval.

**Python SDK (Embedded / HTTP)**

```python
client.add_resource(
"./documents/guide.md",
target="viking://resources/documents/guide.md",
watch_interval=60,
)
```

**HTTP API**

```bash
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "./documents/guide.md",
"target": "viking://resources/documents/guide.md",
"watch_interval": 60
}'
```

**CLI**

```bash
openviking add-resource ./documents/guide.md --to viking://resources/documents/guide.md --watch-interval 60

# Disable watch
openviking add-resource ./documents/guide.md --to viking://resources/documents/guide.md --watch-interval 0
```

---

### export_ovpack()
Expand Down
43 changes: 43 additions & 0 deletions docs/zh/api/02-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Input -> Parser -> TreeBuilder -> AGFS -> SemanticQueue -> Vector Index
| instruction | str | 否 | "" | 特殊处理指令 |
| wait | bool | 否 | False | 等待语义处理完成 |
| timeout | float | 否 | None | 超时时间(秒),仅在 wait=True 时生效 |
| watch_interval | float | 否 | 0 | 定时更新间隔(分钟)。>0 开启/更新定时任务;<=0 关闭(停用)定时任务。仅在指定 target 时生效 |

**Python SDK (Embedded / HTTP)**

Expand Down Expand Up @@ -168,6 +169,48 @@ curl -X POST http://localhost:1933/api/v1/system/wait \
openviking add-resource ./documents/guide.md --wait
```

**示例:开启定时更新(watch_interval)**

`watch_interval` 的单位为分钟,用于对指定的目标 URI 定期触发更新处理:

- `watch_interval > 0`:创建(或重新激活并更新)该 `target` 的定时任务
- `watch_interval <= 0`:关闭(停用)该 `target` 的定时任务
- 只有在指定 `target` / CLI `--to` 时才会创建定时任务

如果同一个 `target` 已存在激活中的定时任务,再次以 `watch_interval > 0` 提交会返回冲突错误;需要先将 `watch_interval` 设为 `0`(取消/停用)后再重新设置新的间隔。

**Python SDK (Embedded / HTTP)**

```python
client.add_resource(
"./documents/guide.md",
target="viking://resources/documents/guide.md",
watch_interval=60,
)
```

**HTTP API**

```bash
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "./documents/guide.md",
"target": "viking://resources/documents/guide.md",
"watch_interval": 60
}'
```

**CLI**

```bash
openviking add-resource ./documents/guide.md --to viking://resources/documents/guide.md --watch-interval 60

# 取消监控
openviking add-resource ./documents/guide.md --to viking://resources/documents/guide.md --watch-interval 0
```

---

### export_ovpack()
Expand Down
151 changes: 151 additions & 0 deletions examples/watch_resource_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
# SPDX-License-Identifier: Apache-2.0
"""
Resource Watch Feature Example

This example demonstrates how to use the resource watch feature in OpenViking.
The watch feature allows you to automatically re-process resources at specified
intervals.

Key features:
- Create resources with watch enabled
- Update watch intervals (cancel then re-create)
- Cancel watch tasks
- Handle conflict errors
"""

import asyncio
from pathlib import Path

from openviking import AsyncOpenViking
from openviking_cli.exceptions import ConflictError


async def example_basic_watch():
client = AsyncOpenViking(path="./data_watch_example")
await client.initialize()

try:
test_file = Path("./test_resource.md")
test_file.write_text(
"""# Test Resource

## Content
This is a test resource for watch functionality.

## Version
Version: 1.0
"""
)

to_uri = "viking://resources/watched_resource"

print("\nAdding resource with watch_interval=60.0 minutes...")
result = await client.add_resource(
path=str(test_file),
to=to_uri,
reason="Example: monitoring a document",
instruction="Check for updates and re-index",
watch_interval=60.0,
)

print("Resource added successfully!")
print(f" Root URI: {result['root_uri']}")
finally:
await client.close()


async def example_update_watch_interval():
client = AsyncOpenViking(path="./data_watch_example")
await client.initialize()

try:
test_file = Path("./test_resource.md")
to_uri = "viking://resources/watched_resource"

print("\nUpdating watch interval by canceling then re-creating...")
await client.add_resource(
path=str(test_file),
to=to_uri,
watch_interval=0,
)
await client.add_resource(
path=str(test_file),
to=to_uri,
reason="Updated: more frequent monitoring",
watch_interval=120.0,
)
print("Watch task updated successfully!")
finally:
await client.close()


async def example_cancel_watch():
client = AsyncOpenViking(path="./data_watch_example")
await client.initialize()

try:
test_file = Path("./test_resource.md")
to_uri = "viking://resources/watched_resource"

print("\nCancelling watch by setting interval to 0...")
await client.add_resource(
path=str(test_file),
to=to_uri,
watch_interval=0,
)
print("Watch task cancelled successfully!")
finally:
await client.close()


async def example_handle_conflict():
client = AsyncOpenViking(path="./data_watch_example")
await client.initialize()

try:
test_file = Path("./test_resource.md")
to_uri = "viking://resources/conflict_example"

print("\nCreating first watch task...")
await client.add_resource(
path=str(test_file),
to=to_uri,
watch_interval=30.0,
)
print(" First watch task created successfully")

print("\nAttempting to create second watch task for same URI...")
try:
await client.add_resource(
path=str(test_file),
to=to_uri,
watch_interval=60.0,
)
print(" ERROR: This should not happen!")
except ConflictError as e:
print(" ConflictError caught as expected!")
print(f" Error message: {e}")
finally:
await client.close()


async def main():
print("\n" + "=" * 60)
print("OpenViking Resource Watch Examples")
print("=" * 60)

await example_basic_watch()
await example_update_watch_interval()
await example_cancel_watch()
await example_handle_conflict()

print("\n" + "=" * 60)
print("All examples completed!")
print("=" * 60)


if __name__ == "__main__":
asyncio.run(main())

6 changes: 6 additions & 0 deletions openviking/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ async def add_resource(
timeout: float = None,
build_index: bool = True,
summarize: bool = False,
watch_interval: float = 0,
telemetry: TelemetryRequest = False,
**kwargs,
) -> Dict[str, Any]:
Expand Down Expand Up @@ -223,9 +224,14 @@ async def add_resource(
build_index=build_index,
summarize=summarize,
telemetry=telemetry,
watch_interval=watch_interval,
**kwargs,
)

@property
def _service(self):
return self._client.service

async def wait_processed(self, timeout: float = None) -> Dict[str, Any]:
"""Wait for all queued processing to complete."""
await self._ensure_initialized()
Expand Down
Loading
Loading